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 (
@@ -698,6 +707,12 @@ def is_gateway_chassis(chassis):
698
707
return constants .CMS_OPT_CHASSIS_AS_GW in get_ovn_cms_options (chassis )
699
708
700
709
710
+ def is_extport_host_chassis (chassis ):
711
+ """Check if the given Chassis is marked to host external ports"""
712
+ return (constants .CMS_OPT_CHASSIS_AS_EXTPORT_HOST in
713
+ get_ovn_cms_options (chassis ))
714
+
715
+
701
716
def get_port_capabilities (port ):
702
717
"""Return a list of port's capabilities"""
703
718
return port .get (portbindings .PROFILE , {}).get (constants .PORT_CAP_PARAM , [])
@@ -751,7 +766,7 @@ def get_chassis_in_azs(chassis_list, az_list):
751
766
return chassis
752
767
753
768
754
- def get_gateway_chassis_without_azs (chassis_list ):
769
+ def get_chassis_without_azs (chassis_list ):
755
770
"""Return a set of Chassis that does not belong to any AZs.
756
771
757
772
Filter a list of Chassis and return only the Chassis that does not
@@ -760,7 +775,7 @@ def get_gateway_chassis_without_azs(chassis_list):
760
775
:param chassis_list: A list of Chassis objects
761
776
:returns: A set of Chassis names
762
777
"""
763
- return {ch .name for ch in chassis_list if is_gateway_chassis ( ch ) and not
778
+ return {ch .name for ch in chassis_list if not
764
779
get_chassis_availability_zones (ch )}
765
780
766
781
@@ -889,77 +904,150 @@ def get_ovn_chassis_other_config(chassis):
889
904
return chassis .external_ids
890
905
891
906
892
- def sync_ha_chassis_group (context , network_id , nb_idl , sb_idl , txn ):
893
- """Return the UUID of the HA Chassis Group or the HA Chassis Group cmd.
894
-
895
- Given the Neutron Network ID, this method will return (or create
896
- and then return) the appropriate HA Chassis Group the external
897
- port (in that network) needs to be associated with.
907
+ def _get_info_for_ha_chassis_group (context , port_id , network_id , sb_idl ):
908
+ """Get the common required information to create a HA Chassis Group.
898
909
899
- :param context: Neutron API context.
900
- :param network_id: The Neutron network ID.
901
- :param nb_idl: OVN NB IDL
902
- :param sb_idl: OVN SB IDL
903
- :param txn: The ovsdbapp transaction object.
904
- :returns: The HA Chassis Group UUID or the HA Chassis Group command object.
910
+ :param context: Neutron API context
911
+ :param port_id: The port ID
912
+ :param network_id: The network ID
913
+ :param sb_idl: OVN SB IDL
914
+ :returns: An instance of HAChassisGroupInfo
905
915
"""
916
+ ignore_chassis = set ()
917
+ # If there are Chassis marked for hosting external ports create a HA
918
+ # Chassis Group per external port, otherwise do it at the network level
919
+ chassis_list = sb_idl .get_extport_chassis_from_cms_options ()
920
+ if chassis_list :
921
+ group_name = ovn_extport_chassis_group_name (port_id )
922
+ # Check if the port is bound to a chassis and if so, ignore that
923
+ # chassis when building the HA Chassis Group to ensure the
924
+ # external port is bound to a different chassis than the VM
925
+ ignore_chassis = sb_idl .get_chassis_host_for_port (port_id )
926
+ LOG .debug ('HA Chassis Group %s is based on external port %s '
927
+ '(network %s)' , group_name , port_id , network_id )
928
+ else :
929
+ chassis_list = sb_idl .get_gateway_chassis_from_cms_options (
930
+ name_only = False )
931
+ group_name = ovn_name (network_id )
932
+ LOG .debug ('HA Chassis Group %s is based on network %s' ,
933
+ group_name , network_id )
934
+
935
+ # Get the Availability Zones hints
906
936
plugin = directory .get_plugin ()
907
937
az_hints = common_utils .get_az_hints (
908
938
plugin .get_network (context , network_id ))
909
939
910
- ha_ch_grp_name = ovn_name (network_id )
911
- ext_ids = {constants .OVN_AZ_HINTS_EXT_ID_KEY : ',' .join (az_hints )}
912
- hcg_cmd = txn .add (nb_idl .ha_chassis_group_add (
913
- ha_ch_grp_name , may_exist = True , external_ids = ext_ids ))
940
+ return HAChassisGroupInfo (
941
+ group_name = group_name , chassis_list = chassis_list , az_hints = az_hints ,
942
+ ignore_chassis = ignore_chassis )
914
943
915
- if isinstance (hcg_cmd .result , rowview .RowView ):
916
- # The HA chassis group existed before this transaction.
917
- ha_ch_grp = hcg_cmd .result
918
- else :
919
- # The HA chassis group is being created in this transaction.
920
- ha_ch_grp = None
921
944
922
- # Get the chassis belonging to the AZ hints
923
- ch_list = sb_idl .get_gateway_chassis_from_cms_options (name_only = False )
924
- if not az_hints :
925
- az_chassis = get_gateway_chassis_without_azs (ch_list )
945
+ def _filter_candidates_for_ha_chassis_group (hcg_info ):
946
+ """Filter a list of chassis candidates for a given HA Chassis Group.
947
+
948
+ Filter a list of chassis candidates for a given HA Chassis Group taking
949
+ in consideration availability zones if present.
950
+
951
+ :param hcg_info: A instance of HAChassisGroupInfo
952
+ :returns: A list of chassis
953
+ """
954
+ if hcg_info .az_hints :
955
+ candidates = get_chassis_in_azs (hcg_info .chassis_list ,
956
+ hcg_info .az_hints )
957
+ LOG .debug ('Taking in consideration the AZs "%s" for HA '
958
+ 'Chassis Group %s' , ',' .join (hcg_info .az_hints ),
959
+ hcg_info .group_name )
926
960
else :
927
- az_chassis = get_chassis_in_azs ( ch_list , az_hints )
961
+ candidates = get_chassis_without_azs ( hcg_info . chassis_list )
928
962
963
+ # Remove the ignored Chassis, if present
964
+ if hcg_info .ignore_chassis :
965
+ LOG .debug ('Ignoring chassis %s for HA Chassis Group %s' ,
966
+ ', ' .join (hcg_info .ignore_chassis ), hcg_info .group_name )
967
+ candidates = candidates - hcg_info .ignore_chassis
968
+
969
+ return candidates
970
+
971
+
972
+ def sync_ha_chassis_group (context , port_id , network_id , nb_idl , sb_idl , txn ):
973
+ """Return the UUID of the HA Chassis Group or the HA Chassis Group cmd.
974
+
975
+ Given the Neutron Network ID, this method will return (or create
976
+ and then return) the appropriate HA Chassis Group the external
977
+ port (in that network) needs to be associated with.
978
+
979
+ :param context: Neutron API context
980
+ :param port_id: The port ID
981
+ :param network_id: The network ID
982
+ :param nb_idl: OVN NB IDL
983
+ :param sb_idl: OVN SB IDL
984
+ :param txn: The ovsdbapp transaction object
985
+ :returns: The HA Chassis Group UUID or the HA Chassis Group command object
986
+ """
987
+ # If there are Chassis marked for hosting external ports create a HA
988
+ # Chassis Group per external port, otherwise do it at the network level
989
+ hcg_info = _get_info_for_ha_chassis_group (context , port_id , network_id ,
990
+ sb_idl )
991
+ candidates = _filter_candidates_for_ha_chassis_group (hcg_info )
992
+
993
+ # Try to get the HA Chassis Group or create if it doesn't exist
994
+ ha_ch_grp = ha_ch_grp_cmd = None
995
+ try :
996
+ ha_ch_grp = nb_idl .ha_chassis_group_get (
997
+ hcg_info .group_name ).execute (check_error = True )
998
+ except idlutils .RowNotFound :
999
+ ext_ids = {constants .OVN_AZ_HINTS_EXT_ID_KEY : ',' .join (
1000
+ hcg_info .az_hints )}
1001
+ ha_ch_grp_cmd = txn .add (nb_idl .ha_chassis_group_add (
1002
+ hcg_info .group_name , may_exist = True , external_ids = ext_ids ))
1003
+
1004
+ max_chassis_number = constants .MAX_CHASSIS_IN_HA_GROUP
929
1005
priority = constants .HA_CHASSIS_GROUP_HIGHEST_PRIORITY
1006
+
1007
+ # Check if the HA Chassis Group existed before. If so, re-calculate
1008
+ # the canditates in case something changed and keep the highest priority
1009
+ # chassis in the group (if it's an eligible candidate) with the highest
1010
+ # priority to avoid external ports from moving around
930
1011
if ha_ch_grp :
931
1012
# Remove any chassis that no longer belongs to the AZ hints
1013
+ # or is ignored
932
1014
all_ch = {ch .chassis_name for ch in ha_ch_grp .ha_chassis }
933
- ch_to_del = all_ch - az_chassis
1015
+ ch_to_del = all_ch - candidates
934
1016
for ch in ch_to_del :
935
1017
txn .add (nb_idl .ha_chassis_group_del_chassis (
936
- ha_ch_grp_name , ch , if_exists = True ))
1018
+ hcg_info . group_name , ch , if_exists = True ))
937
1019
938
- # Find the highest priority chassis in the HA Chassis Group. If
939
- # it exists and still belongs to the same AZ, keep it as the
940
- # highest priority in the group to avoid ports already bond to it
941
- # from moving to another chassis.
1020
+ # Find the highest priority chassis in the HA Chassis Group
942
1021
high_prio_ch = max (ha_ch_grp .ha_chassis , key = lambda x : x .priority ,
943
1022
default = None )
944
- priority = constants .HA_CHASSIS_GROUP_HIGHEST_PRIORITY
945
- if high_prio_ch and high_prio_ch .chassis_name in az_chassis :
1023
+ if (high_prio_ch and
1024
+ high_prio_ch .chassis_name in candidates ):
1025
+ # If found, keep it as the highest priority chassis in the group
946
1026
txn .add (nb_idl .ha_chassis_group_add_chassis (
947
- ha_ch_grp_name , high_prio_ch .chassis_name ,
1027
+ hcg_info . group_name , high_prio_ch .chassis_name ,
948
1028
priority = priority ))
949
- az_chassis .remove (high_prio_ch .chassis_name )
1029
+ candidates .remove (high_prio_ch .chassis_name )
950
1030
priority -= 1
951
-
952
- # Randomize the order so that networks belonging to the same
953
- # availability zones do not necessarily end up with the same
954
- # Chassis as the highest priority one.
955
- for ch in random .sample (list (az_chassis ), len (az_chassis )):
1031
+ max_chassis_number -= 1
1032
+ LOG .debug ('Keeping chassis %s as the highest priority chassis '
1033
+ 'for HA Chassis Group %s' , high_prio_ch .chassis_name ,
1034
+ hcg_info .group_name )
1035
+
1036
+ # random.sample() second parameter needs to be <= the list size,
1037
+ # that's why we need to check for the max value here
1038
+ max_chassis_number = min (max_chassis_number , len (candidates ))
1039
+ # Limit the number of members and randomize the order so each group,
1040
+ # even if they belonging to the same availability zones do not
1041
+ # necessarily end up with the same Chassis as the highest priority one.
1042
+ for ch in random .sample (list (candidates ), max_chassis_number ):
956
1043
txn .add (nb_idl .ha_chassis_group_add_chassis (
957
- hcg_cmd , ch , priority = priority ))
1044
+ hcg_info . group_name , ch , priority = priority ))
958
1045
priority -= 1
959
1046
1047
+ LOG .info ('HA Chassis Group %s synchronized' , hcg_info .group_name )
960
1048
# Return the existing register UUID or the HA chassis group creation
961
1049
# command (see ovsdbapp ``HAChassisGroupAddChassisCommand`` class).
962
- return ha_ch_grp .uuid if ha_ch_grp else hcg_cmd
1050
+ return ha_ch_grp .uuid if ha_ch_grp else ha_ch_grp_cmd
963
1051
964
1052
965
1053
def get_subnets_address_scopes (context , subnets , fixed_ips , ml2_plugin ):
@@ -1117,3 +1205,9 @@ def get_requested_chassis(requested_chassis):
1117
1205
if isinstance (requested_chassis , str ):
1118
1206
return requested_chassis .split (',' )
1119
1207
return []
1208
+
1209
+
1210
+ # TODO(lucasagomes): Remove this function when the additional_chassis column
1211
+ # becomes the norm and older versions of OVN are no longer supported
1212
+ def is_additional_chassis_supported (idl ):
1213
+ return idl .is_col_present ('Port_Binding' , 'additional_chassis' )
0 commit comments