components/openstack/neutron/files/agent/solaris/interface.py
changeset 6848 8e252a37ed0d
parent 6555 321727f908b3
child 7178 5dc920725250
equal deleted inserted replaced
6847:57069587975f 6848:8e252a37ed0d
    13 #    under the License.
    13 #    under the License.
    14 #
    14 #
    15 # @author: Girish Moodalbail, Oracle, Inc.
    15 # @author: Girish Moodalbail, Oracle, Inc.
    16 
    16 
    17 from openstack_common import get_ovsdb_info
    17 from openstack_common import get_ovsdb_info
    18 import rad.client as radcli
       
    19 import rad.connect as radcon
       
    20 import rad.bindings.com.oracle.solaris.rad.evscntl_1 as evsbind
       
    21 
    18 
    22 from oslo_config import cfg
    19 from oslo_config import cfg
    23 from oslo_log import log as logging
    20 from oslo_log import log as logging
    24 from oslo_serialization import jsonutils
    21 from oslo_serialization import jsonutils
    25 
    22 
    31 
    28 
    32 
    29 
    33 LOG = logging.getLogger(__name__)
    30 LOG = logging.getLogger(__name__)
    34 
    31 
    35 OPTS = [
    32 OPTS = [
    36     cfg.StrOpt('evs_controller', default='ssh://evsuser@localhost',
    33     cfg.StrOpt('admin_user',
    37                help=_("An URI that specifies an EVS controller"))
    34                help=_("Admin username")),
       
    35     cfg.StrOpt('admin_password',
       
    36                help=_("Admin password"),
       
    37                secret=True),
       
    38     cfg.StrOpt('admin_tenant_name',
       
    39                help=_("Admin tenant name")),
       
    40     cfg.StrOpt('auth_url',
       
    41                help=_("Authentication URL")),
       
    42     cfg.StrOpt('auth_strategy', default='keystone',
       
    43                help=_("The type of authentication to use")),
       
    44     cfg.StrOpt('auth_region',
       
    45                help=_("Authentication region")),
       
    46     cfg.StrOpt('endpoint_type',
       
    47                default='publicURL',
       
    48                help=_("Network service endpoint type to pull from "
       
    49                       "the keystone catalog")),
    38 ]
    50 ]
    39 
    51 
    40 
    52 
    41 class EVSControllerError(exceptions.NeutronException):
    53 class OVSInterfaceDriver(object):
    42     message = _("EVS controller: %(errmsg)s")
    54     """Driver used to manage Solaris OVS VNICs.
    43 
    55 
    44     def __init__(self, evs_errmsg):
    56     This class provides methods to create/delete a Crossbow VNIC and
    45         super(EVSControllerError, self).__init__(errmsg=evs_errmsg)
    57     add it as a port of OVS bridge.
    46 
    58     TODO(gmoodalb): More methods to implement here for MITAKA??
    47 
       
    48 class SolarisVNICDriver(object):
       
    49     """Driver used to manage Solaris EVS VNICs.
       
    50 
       
    51     This class provides methods to create/delete an EVS VNIC and
       
    52     plumb/unplumb ab IP interface and addresses on the EVS VNIC.
       
    53     """
    59     """
    54 
    60 
    55     # TODO(gmoodalb): dnsmasq uses old style `ifreq', so 16 is the maximum
    61     # TODO(gmoodalb): dnsmasq uses old style `ifreq', so 16 is the maximum
    56     # length including the NUL character. If we change it to use new style
    62     # length including the NUL character. If we change it to use new style
    57     # `lifreq', then we will be able to extend the length to 32 characters.
    63     # `lifreq', then we will be able to extend the length to 32 characters.
    58     VNIC_NAME_MAXLEN = 15
    64     VNIC_NAME_MAXLEN = 15
    59     VNIC_NAME_PREFIX = 'dh'
    65     VNIC_NAME_PREFIX = 'dh'
    60     VNIC_NAME_SUFFIX = '_0'
    66     VNIC_NAME_SUFFIX = '_0'
    61     VNIC_NAME_LEN_WO_SUFFIX = VNIC_NAME_MAXLEN - \
    67     VNIC_NAME_LEN_WO_SUFFIX = VNIC_NAME_MAXLEN - \
    62         len(VNIC_NAME_SUFFIX)
    68         len(VNIC_NAME_SUFFIX)
    63 
       
    64     def __init__(self, conf):
       
    65         self.conf = conf
       
    66         try:
       
    67             self.rad_uri = radcon.RadURI(conf.evs_controller)
       
    68         except ValueError as err:
       
    69             raise SystemExit(_("Specified evs_controller is invalid: %s"), err)
       
    70 
       
    71         self._rad_connection = None
       
    72         # set the controller property for this host
       
    73         cmd = ['/usr/sbin/evsadm', 'show-prop', '-co', 'value', '-p',
       
    74                'controller']
       
    75         stdout = utils.execute(cmd)
       
    76         if conf.evs_controller != stdout.strip():
       
    77             cmd = ['/usr/sbin/evsadm', 'set-prop', '-p',
       
    78                    'controller=%s' % (conf.evs_controller)]
       
    79             utils.execute(cmd)
       
    80 
       
    81     @property
       
    82     def rad_connection(self):
       
    83         if (self._rad_connection is not None and
       
    84                 self._rad_connection._closed is None):
       
    85             return self._rad_connection
       
    86 
       
    87         LOG.debug(_("Connecting to EVS Controller at %s") %
       
    88                   self.conf.evs_controller)
       
    89 
       
    90         self._rad_connection = self.rad_uri.connect()
       
    91         return self._rad_connection
       
    92 
       
    93     def fini_l3(self, device_name):
       
    94         ipif = net_lib.IPInterface(device_name)
       
    95         ipif.delete_ip()
       
    96 
       
    97     def init_l3(self, device_name, ip_cidrs, addrconf=False):
       
    98         """Set the L3 settings for the interface using data from the port.
       
    99            ip_cidrs: list of 'X.X.X.X/YY' strings
       
   100         """
       
   101         ipif = net_lib.IPInterface(device_name)
       
   102         for ip_cidr in ip_cidrs:
       
   103             ipif.create_address(ip_cidr)
       
   104         if addrconf:
       
   105             ipif.create_addrconf()
       
   106 
       
   107     # TODO(gmoodalb): - probably take PREFIX?? for L3
       
   108     def get_device_name(self, port):
       
   109         vnicname = (self.VNIC_NAME_PREFIX +
       
   110                     port.id)[:self.VNIC_NAME_LEN_WO_SUFFIX]
       
   111         vnicname += self.VNIC_NAME_SUFFIX
       
   112         return vnicname.replace('-', '_')
       
   113 
       
   114     def plug(self, tenant_id, network_id, port_id, datalink_name, mac_address,
       
   115              network=None, bridge=None, namespace=None, prefix=None,
       
   116              protection=False, vif_type=None):
       
   117         """Plug in the interface."""
       
   118 
       
   119         if net_lib.Datalink.datalink_exists(datalink_name):
       
   120             LOG.info(_("Device %s already exists"), datalink_name)
       
   121             return
       
   122 
       
   123         if datalink_name.startswith('l3e'):
       
   124             # verify external network parameter settings
       
   125             dl = net_lib.Datalink(datalink_name)
       
   126             # determine the network type of the external network
       
   127             # TODO(gmoodalb): use EVS RAD APIs
       
   128             evsname = network_id
       
   129             cmd = ['/usr/sbin/evsadm', 'show-evs', '-co', 'l2type,vid',
       
   130                    '-f', 'evs=%s' % evsname]
       
   131             try:
       
   132                 stdout = utils.execute(cmd)
       
   133             except Exception as err:
       
   134                 LOG.error(_("Failed to retrieve the network type for "
       
   135                             "the external network, and it is required "
       
   136                             "to create an external gateway port: %s") % err)
       
   137                 return
       
   138             output = stdout.splitlines()[0].strip()
       
   139             l2type, vid = output.split(':')
       
   140             if l2type != 'flat' and l2type != 'vlan':
       
   141                 LOG.error(_("External network should be either Flat or "
       
   142                             "VLAN based, and it is required to "
       
   143                             "create an external gateway port"))
       
   144                 return
       
   145             elif (l2type == 'vlan' and
       
   146                   self.conf.get("external_network_datalink", None)):
       
   147                 LOG.warning(_("external_network_datalink is deprecated in "
       
   148                               "Juno and will be removed in the next release "
       
   149                               "of Solaris OpenStack. Please use the evsadm "
       
   150                               "set-controlprop subcommand to setup the "
       
   151                               "uplink-port for an external network"))
       
   152                 # proceed with the old-style of doing things
       
   153                 dl.create_vnic(self.conf.external_network_datalink,
       
   154                                mac_address=mac_address, vid=vid)
       
   155                 return
       
   156 
       
   157         try:
       
   158             evsc = self.rad_connection.get_object(evsbind.EVSController())
       
   159             vports_info = evsc.getVPortInfo("vport=%s" % (port_id))
       
   160             if vports_info:
       
   161                 vport_info = vports_info[0]
       
   162                 # This is to handle HA when the 1st DHCP/L3 agent is down and
       
   163                 # the second DHCP/L3 agent tries to connect its VNIC to EVS, we
       
   164                 # will end up in "vport in use" error. So, we need to reset the
       
   165                 # vport before we connect the VNIC to EVS.
       
   166                 if vport_info.status == evsbind.VPortStatus.USED:
       
   167                     LOG.debug(_("Retrieving EVS: %s"), vport_info.evsuuid)
       
   168                     pat = radcli.ADRGlobPattern({'uuid': network_id,
       
   169                                                  'tenant': tenant_id})
       
   170                     evs_objs = self.rad_connection.list_objects(evsbind.EVS(),
       
   171                                                                 pat)
       
   172                     if evs_objs:
       
   173                         evs = self.rad_connection.get_object(evs_objs[0])
       
   174                         evs.resetVPort(port_id, "force=yes")
       
   175 
       
   176                 if not protection:
       
   177                     LOG.debug(_("Retrieving VPort: %s"), port_id)
       
   178                     pat = radcli.ADRGlobPattern({'uuid': port_id,
       
   179                                                  'tenant': tenant_id,
       
   180                                                  'evsuuid': network_id})
       
   181                     vport_objs = self.rad_connection.list_objects(
       
   182                         evsbind.VPort(), pat)
       
   183                     if vport_objs:
       
   184                         vport = self.rad_connection.get_object(vport_objs[0])
       
   185                         vport.setProperty("protection=none")
       
   186         except radcli.ObjectError as oe:
       
   187             raise EVSControllerError(oe.get_payload().errmsg)
       
   188 
       
   189         dl = net_lib.Datalink(datalink_name)
       
   190         evs_vport = "%s/%s" % (network_id, port_id)
       
   191         dl.connect_vnic(evs_vport, tenant_id)
       
   192 
       
   193     def unplug(self, device_name, namespace=None, prefix=None):
       
   194         """Unplug the interface."""
       
   195 
       
   196         dl = net_lib.Datalink(device_name)
       
   197         dl.delete_vnic()
       
   198 
       
   199 
       
   200 class OVSInterfaceDriver(SolarisVNICDriver):
       
   201     """Driver used to manage Solaris OVS VNICs.
       
   202 
       
   203     This class provides methods to create/delete a Crossbow VNIC and
       
   204     add it as a port of OVS bridge.
       
   205     """
       
   206 
    69 
   207     def __init__(self, conf):
    70     def __init__(self, conf):
   208         self.conf = conf
    71         self.conf = conf
   209         self._neutron_client = None
    72         self._neutron_client = None
   210 
    73 
   222             region_name=self.conf.auth_region,
    85             region_name=self.conf.auth_region,
   223             endpoint_type=self.conf.endpoint_type
    86             endpoint_type=self.conf.endpoint_type
   224         )
    87         )
   225         return self._neutron_client
    88         return self._neutron_client
   226 
    89 
       
    90     def fini_l3(self, device_name):
       
    91         ipif = net_lib.IPInterface(device_name)
       
    92         ipif.delete_ip()
       
    93 
       
    94     def init_l3(self, device_name, ip_cidrs, addrconf=False):
       
    95         """Set the L3 settings for the interface using data from the port.
       
    96            ip_cidrs: list of 'X.X.X.X/YY' strings
       
    97         """
       
    98         ipif = net_lib.IPInterface(device_name)
       
    99         for ip_cidr in ip_cidrs:
       
   100             ipif.create_address(ip_cidr)
       
   101         if addrconf:
       
   102             ipif.create_addrconf()
       
   103 
       
   104     # TODO(gmoodalb): - probably take PREFIX?? for L3
       
   105     def get_device_name(self, port):
       
   106         vnicname = (self.VNIC_NAME_PREFIX +
       
   107                     port.id)[:self.VNIC_NAME_LEN_WO_SUFFIX]
       
   108         vnicname += self.VNIC_NAME_SUFFIX
       
   109         return vnicname.replace('-', '_')
       
   110 
   227     def plug(self, tenant_id, network_id, port_id, datalink_name, mac_address,
   111     def plug(self, tenant_id, network_id, port_id, datalink_name, mac_address,
   228              network=None, bridge=None, namespace=None, prefix=None,
   112              network=None, bridge=None, namespace=None, prefix=None,
   229              protection=False, vif_type=None):
   113              protection=False, mtu=None, vif_type=None):
   230         """Plug in the interface."""
   114         """Plug in the interface."""
   231 
   115 
   232         if net_lib.Datalink.datalink_exists(datalink_name):
   116         if net_lib.Datalink.datalink_exists(datalink_name):
   233             LOG.info(_("Device %s already exists"), datalink_name)
   117             LOG.info(_("Device %s already exists"), datalink_name)
   234             return
   118             return
   310         ovs.replace_port(datalink_name, *attrs)
   194         ovs.replace_port(datalink_name, *attrs)
   311 
   195 
   312     def unplug(self, datalink_name, bridge=None, namespace=None, prefix=None):
   196     def unplug(self, datalink_name, bridge=None, namespace=None, prefix=None):
   313         """Unplug the interface."""
   197         """Unplug the interface."""
   314 
   198 
       
   199         # remove any IP addresses on top of this datalink, otherwise we will
       
   200         # get 'device busy' error while deleting the datalink
       
   201         self.fini_l3(datalink_name)
       
   202 
   315         dl = net_lib.Datalink(datalink_name)
   203         dl = net_lib.Datalink(datalink_name)
   316         dl.delete_vnic()
   204         dl.delete_vnic()
   317 
   205 
   318         if bridge is None:
   206         if bridge is None:
   319             bridge = self.conf.ovs_integration_bridge
   207             bridge = self.conf.ovs_integration_bridge
   327             ovs.delete_port(datalink_name)
   215             ovs.delete_port(datalink_name)
   328             LOG.debug("Unplugged interface '%s'", datalink_name)
   216             LOG.debug("Unplugged interface '%s'", datalink_name)
   329         except RuntimeError as err:
   217         except RuntimeError as err:
   330             LOG.error(_("Failed unplugging interface '%s': %s") %
   218             LOG.error(_("Failed unplugging interface '%s': %s") %
   331                       (datalink_name, err))
   219                       (datalink_name, err))
       
   220 
       
   221     @property
       
   222     def use_gateway_ips(self):
       
   223         """Whether to use gateway IPs instead of unique IP allocations.
       
   224 
       
   225         In each place where the DHCP agent runs, and for each subnet for
       
   226         which DHCP is handling out IP addresses, the DHCP port needs -
       
   227         at the Linux level - to have an IP address within that subnet.
       
   228         Generally this needs to be a unique Neutron-allocated IP
       
   229         address, because the subnet's underlying L2 domain is bridged
       
   230         across multiple compute hosts and network nodes, and for HA
       
   231         there may be multiple DHCP agents running on that same bridged
       
   232         L2 domain.
       
   233 
       
   234         However, if the DHCP ports - on multiple compute/network nodes
       
   235         but for the same network - are _not_ bridged to each other,
       
   236         they do not need each to have a unique IP address.  Instead
       
   237         they can all share the same address from the relevant subnet.
       
   238         This works, without creating any ambiguity, because those
       
   239         ports are not all present on the same L2 domain, and because
       
   240         no data within the network is ever sent to that address.
       
   241         (DHCP requests are broadcast, and it is the network's job to
       
   242         ensure that such a broadcast will reach at least one of the
       
   243         available DHCP servers.  DHCP responses will be sent _from_
       
   244         the DHCP port address.)
       
   245 
       
   246         Specifically, for networking backends where it makes sense,
       
   247         the DHCP agent allows all DHCP ports to use the subnet's
       
   248         gateway IP address, and thereby to completely avoid any unique
       
   249         IP address allocation.  This behaviour is selected by running
       
   250         the DHCP agent with a configured interface driver whose
       
   251         'use_gateway_ips' property is True.
       
   252 
       
   253         When an operator deploys Neutron with an interface driver that
       
   254         makes use_gateway_ips True, they should also ensure that a
       
   255         gateway IP address is defined for each DHCP-enabled subnet,
       
   256         and that the gateway IP address doesn't change during the
       
   257         subnet's lifetime.
       
   258         """
       
   259         return False