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 |
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 |