|
1 # vim: tabstop=4 shiftwidth=4 softtabstop=4 |
|
2 |
|
3 # Copyright 2012 Nicira Networks, Inc. All rights reserved. |
|
4 # |
|
5 # Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. |
|
6 # |
|
7 # Licensed under the Apache License, Version 2.0 (the "License"); you may |
|
8 # not use this file except in compliance with the License. You may obtain |
|
9 # a copy of the License at |
|
10 # |
|
11 # http://www.apache.org/licenses/LICENSE-2.0 |
|
12 # |
|
13 # Unless required by applicable law or agreed to in writing, software |
|
14 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
15 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
16 # License for the specific language governing permissions and limitations |
|
17 # under the License. |
|
18 # |
|
19 # @author: Dan Wendlandt, Nicira, Inc |
|
20 # @author: Girish Moodalbail, Oracle, Inc. |
|
21 # |
|
22 |
|
23 """ |
|
24 Based off generic l3_agent (quantum/agent/l3_agent) code |
|
25 """ |
|
26 |
|
27 import sqlalchemy as sa |
|
28 |
|
29 from quantum.api.v2 import attributes |
|
30 from quantum.common import constants as l3_constants |
|
31 from quantum.common import exceptions as q_exc |
|
32 from quantum.db import l3_db |
|
33 from quantum.extensions import l3 |
|
34 from quantum.openstack.common import log as logging |
|
35 from quantum.openstack.common import uuidutils |
|
36 from quantum.plugins.evs.db import api as evs_db |
|
37 |
|
38 |
|
39 LOG = logging.getLogger(__name__) |
|
40 |
|
41 DEVICE_OWNER_ROUTER_INTF = l3_constants.DEVICE_OWNER_ROUTER_INTF |
|
42 DEVICE_OWNER_ROUTER_GW = l3_constants.DEVICE_OWNER_ROUTER_GW |
|
43 DEVICE_OWNER_FLOATINGIP = l3_constants.DEVICE_OWNER_FLOATINGIP |
|
44 |
|
45 |
|
46 class Router(evs_db.EVS_DB_BASE): |
|
47 """Represents a v2 quantum router.""" |
|
48 |
|
49 id = sa.Column(sa.String(36), primary_key=True, |
|
50 default=uuidutils.generate_uuid) |
|
51 name = sa.Column(sa.String(255)) |
|
52 status = sa.Column(sa.String(16)) |
|
53 admin_state_up = sa.Column(sa.Boolean) |
|
54 tenant_id = sa.Column(sa.String(255)) |
|
55 gw_port_id = sa.Column(sa.String(36)) |
|
56 gw_port_network_id = sa.Column(sa.String(36)) |
|
57 |
|
58 |
|
59 class FloatingIP(evs_db.EVS_DB_BASE): |
|
60 """Represents a floating IP, which may or many not be |
|
61 allocated to a tenant, and may or may not be associated with |
|
62 an internal port/ip address/router.""" |
|
63 |
|
64 id = sa.Column(sa.String(36), primary_key=True, |
|
65 default=uuidutils.generate_uuid) |
|
66 floating_ip_address = sa.Column(sa.String(64), nullable=False) |
|
67 floating_network_id = sa.Column(sa.String(36), nullable=False) |
|
68 floating_port_id = sa.Column(sa.String(36), nullable=False) |
|
69 fixed_port_id = sa.Column(sa.String(36)) |
|
70 fixed_ip_address = sa.Column(sa.String(64)) |
|
71 router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id')) |
|
72 tenant_id = sa.Column(sa.String(255)) |
|
73 |
|
74 |
|
75 class EVS_L3_NAT_db_mixin(l3_db.L3_NAT_db_mixin): |
|
76 """Mixin class to add L3/NAT router methods""" |
|
77 |
|
78 Router = Router |
|
79 FloatingIP = FloatingIP |
|
80 |
|
81 def _make_router_dict(self, router, fields=None): |
|
82 res = {'id': router['id'], |
|
83 'name': router['name'], |
|
84 'tenant_id': router['tenant_id'], |
|
85 'admin_state_up': router['admin_state_up'], |
|
86 'status': router['status'], |
|
87 'external_gateway_info': None} |
|
88 if router['gw_port_id']: |
|
89 nw_id = router['gw_port_network_id'] |
|
90 res['external_gateway_info'] = {'network_id': nw_id} |
|
91 return self._fields(res, fields) |
|
92 |
|
93 def create_router(self, context, router): |
|
94 return super(EVS_L3_NAT_db_mixin, self).\ |
|
95 create_router(evs_db.get_evs_context(context), router) |
|
96 |
|
97 def update_router(self, context, id, router): |
|
98 return super(EVS_L3_NAT_db_mixin, self).\ |
|
99 update_router(evs_db.get_evs_context(context), id, router) |
|
100 |
|
101 def _update_router_gw_info(self, context, router_id, info): |
|
102 """This method overrides the base class method and it's contents |
|
103 are exactly same as the base class method except that the Router |
|
104 DB columns are different for EVS and OVS""" |
|
105 |
|
106 router = self._get_router(context, router_id) |
|
107 gw_port_id = router['gw_port_id'] |
|
108 gw_port_network_id = router['gw_port_network_id'] |
|
109 |
|
110 network_id = info.get('network_id', None) if info else None |
|
111 if network_id: |
|
112 self._get_network(context, network_id) |
|
113 if not self._network_is_external(context, network_id): |
|
114 msg = _("Network %s is not a valid external " |
|
115 "network") % network_id |
|
116 raise q_exc.BadRequest(resource='router', msg=msg) |
|
117 |
|
118 # figure out if we need to delete existing port |
|
119 if gw_port_id and gw_port_network_id != network_id: |
|
120 fip_count = self.get_floatingips_count(context.elevated(), |
|
121 {'router_id': [router_id]}) |
|
122 if fip_count: |
|
123 raise l3.RouterExternalGatewayInUseByFloatingIp( |
|
124 router_id=router_id, net_id=gw_port_network_id) |
|
125 with context.session.begin(subtransactions=True): |
|
126 router['gw_port_id'] = None |
|
127 router['gw_port_network_id'] = None |
|
128 context.session.add(router) |
|
129 self.delete_port(context.elevated(), gw_port_id, |
|
130 l3_port_check=False) |
|
131 |
|
132 if network_id is not None and (gw_port_id is None or |
|
133 gw_port_network_id != network_id): |
|
134 subnets = self._get_subnets_by_network(context, |
|
135 network_id) |
|
136 for subnet in subnets: |
|
137 self._check_for_dup_router_subnet(context, router_id, |
|
138 network_id, subnet['id'], |
|
139 subnet['cidr']) |
|
140 |
|
141 # Port has no 'tenant-id', as it is hidden from user |
|
142 gw_port = self.create_port(context.elevated(), { |
|
143 'port': |
|
144 {'tenant_id': '', # intentionally not set |
|
145 'network_id': network_id, |
|
146 'mac_address': attributes.ATTR_NOT_SPECIFIED, |
|
147 'fixed_ips': attributes.ATTR_NOT_SPECIFIED, |
|
148 'device_id': router_id, |
|
149 'device_owner': DEVICE_OWNER_ROUTER_GW, |
|
150 'admin_state_up': True, |
|
151 'name': ''}}) |
|
152 |
|
153 if not len(gw_port['fixed_ips']): |
|
154 self.delete_port(context.elevated(), gw_port['id'], |
|
155 l3_port_check=False) |
|
156 msg = (_('No IPs available for external network %s') % |
|
157 network_id) |
|
158 raise q_exc.BadRequest(resource='router', msg=msg) |
|
159 |
|
160 with context.session.begin(subtransactions=True): |
|
161 router['gw_port_id'] = gw_port['id'] |
|
162 router['gw_port_network_id'] = gw_port['network_id'] |
|
163 context.session.add(router) |
|
164 |
|
165 def delete_router(self, context, id): |
|
166 super(EVS_L3_NAT_db_mixin, self).\ |
|
167 delete_router(evs_db.get_evs_context(context), id) |
|
168 |
|
169 def get_router(self, context, id, fields=None): |
|
170 return super(EVS_L3_NAT_db_mixin, self).\ |
|
171 get_router(evs_db.get_evs_context(context), id, fields) |
|
172 |
|
173 def get_routers(self, context, filters=None, fields=None, |
|
174 sorts=None, limit=None, marker=None, page_reverse=False): |
|
175 |
|
176 query = evs_db.get_session().query(self.Router) |
|
177 if filters is not None: |
|
178 for key, value in filters.iteritems(): |
|
179 column = getattr(self.Router, key, None) |
|
180 if column: |
|
181 query = query.filter(column.in_(value)) |
|
182 |
|
183 routers = query.all() |
|
184 retlist = [] |
|
185 for router in routers: |
|
186 retlist.append(self._make_router_dict(router, fields)) |
|
187 return retlist |
|
188 |
|
189 def get_routers_count(self, context, filters=None): |
|
190 return len(self.get_routers(context, filters)) |
|
191 |
|
192 def add_router_interface(self, context, router_id, interface_info): |
|
193 return super(EVS_L3_NAT_db_mixin, self).\ |
|
194 add_router_interface(evs_db.get_evs_context(context), |
|
195 router_id, interface_info) |
|
196 |
|
197 def remove_router_interface(self, context, router_id, interface_info): |
|
198 super(EVS_L3_NAT_db_mixin, self).\ |
|
199 remove_router_interface(evs_db.get_evs_context(context), |
|
200 router_id, interface_info) |
|
201 |
|
202 def create_floatingip(self, context, floatingip): |
|
203 return super(EVS_L3_NAT_db_mixin, self).\ |
|
204 create_floatingip(evs_db.get_evs_context(context), floatingip) |
|
205 |
|
206 def update_floatingip(self, context, id, floatingip): |
|
207 return super(EVS_L3_NAT_db_mixin, self).\ |
|
208 update_floatingip(evs_db.get_evs_context(context), id, floatingip) |
|
209 |
|
210 def delete_floatingip(self, context, id): |
|
211 super(EVS_L3_NAT_db_mixin, self).\ |
|
212 delete_floatingip(evs_db.get_evs_context(context), id) |
|
213 |
|
214 def get_floatingip(self, context, id, fields=None): |
|
215 return super(EVS_L3_NAT_db_mixin, self).\ |
|
216 get_floatingip(evs_db.get_evs_context(context), id, fields) |
|
217 |
|
218 def get_floatingips(self, context, filters=None, fields=None, |
|
219 sorts=None, limit=None, marker=None, |
|
220 page_reverse=False): |
|
221 |
|
222 query = evs_db.get_session().query(self.FloatingIP) |
|
223 if filters: |
|
224 for key, value in filters.iteritems(): |
|
225 column = getattr(self.FloatingIP, key, None) |
|
226 if column: |
|
227 query = query.filter(column.in_(value)) |
|
228 |
|
229 floatingips = query.all() |
|
230 retlist = [] |
|
231 for floatingip in floatingips: |
|
232 retlist.append(self._make_floatingip_dict(floatingip, fields)) |
|
233 return retlist |
|
234 |
|
235 def get_floatingips_count(self, context, filters=None): |
|
236 return len(self.get_floatingips(context, filters)) |
|
237 |
|
238 def prevent_l3_port_deletion(self, context, port_id): |
|
239 """ Checks to make sure a port is allowed to be deleted, raising |
|
240 an exception if this is not the case. This should be called by |
|
241 any plugin when the API requests the deletion of a port, since |
|
242 some ports for L3 are not intended to be deleted directly via a |
|
243 DELETE to /ports, but rather via other API calls that perform the |
|
244 proper deletion checks. |
|
245 """ |
|
246 port = self.get_port(context, port_id) |
|
247 if port['device_owner'] in [DEVICE_OWNER_ROUTER_INTF, |
|
248 DEVICE_OWNER_ROUTER_GW, |
|
249 DEVICE_OWNER_FLOATINGIP]: |
|
250 raise l3.L3PortInUse(port_id=port_id, |
|
251 device_owner=port['device_owner']) |
|
252 |
|
253 def disassociate_floatingips(self, context, port_id): |
|
254 super(EVS_L3_NAT_db_mixin, self).\ |
|
255 disassociate_floatingips(evs_db.get_evs_context(context), port_id) |
|
256 |
|
257 def _network_is_external(self, context, net_id): |
|
258 try: |
|
259 evs = self.get_network(context, net_id) |
|
260 return evs[l3.EXTERNAL] |
|
261 except: |
|
262 return False |
|
263 |
|
264 def get_sync_data(self, context, router_ids=None, active=None): |
|
265 return super(EVS_L3_NAT_db_mixin, self).\ |
|
266 get_sync_data(evs_db.get_evs_context(context), router_ids, active) |
|
267 |
|
268 def get_external_network_id(self, context): |
|
269 return super(EVS_L3_NAT_db_mixin, self).\ |
|
270 get_external_network_id(evs_db.get_evs_context(context)) |
|
271 |
|
272 def _get_tenant_id_for_create(self, context, resource): |
|
273 if context.is_admin and 'tenant_id' in resource: |
|
274 tenant_id = resource['tenant_id'] |
|
275 elif ('tenant_id' in resource and |
|
276 resource['tenant_id'] != context.tenant_id): |
|
277 reason = _('Cannot create resource for another tenant') |
|
278 raise q_exc.AdminRequired(reason=reason) |
|
279 else: |
|
280 tenant_id = context.tenant_id |
|
281 return tenant_id |
|
282 |
|
283 def _get_by_id(self, context, model, id): |
|
284 return context.session.query(model).\ |
|
285 filter(model.id == id).one() |
|
286 |
|
287 def _get_network(self, context, network_id): |
|
288 return self.get_network(context, network_id) |
|
289 |
|
290 def _get_subnet(self, context, subnet_id): |
|
291 return self.get_subnet(context, subnet_id) |
|
292 |
|
293 def _get_port(self, context, port_id): |
|
294 return self.get_port(context, port_id) |
|
295 |
|
296 def _delete_port(self, context, port_id): |
|
297 return self.delete_port(context, port_id) |
|
298 |
|
299 def _get_subnets_by_network(self, context, network_id): |
|
300 return self.get_subnets(context, filters={'network_id': network_id}) |
|
301 |
|
302 def allow_l3_port_deletion(self, context, port_id): |
|
303 """ If an L3 agent is using this port, then we need to send |
|
304 a notification to L3 agent to release the port before we can |
|
305 delete the port""" |
|
306 pass |