1
# Copyright 2013 VMware, Inc
3
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
# not use this file except in compliance with the License. You may obtain
5
# a copy of the License at
7
# http://www.apache.org/licenses/LICENSE-2.0
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
# License for the specific language governing permissions and limitations
15
from oslo.serialization import jsonutils
16
from oslo.utils import excutils
18
from neutron.i18n import _LE
19
from neutron.openstack.common import log as logging
20
from neutron.plugins.vmware.common import utils
21
from neutron.plugins.vmware.vshield.common import constants as vcns_const
22
from neutron.plugins.vmware.vshield.common import exceptions
23
from neutron.plugins.vmware.vshield.tasks import constants
24
from neutron.plugins.vmware.vshield.tasks import tasks
26
LOG = logging.getLogger(__name__)
29
class EdgeApplianceDriver(object):
31
# store the last task per edge that has the latest config
37
def _assemble_edge(self, name, appliance_size="compact",
38
deployment_container_id=None, datacenter_moid=None,
39
enable_aesni=True, hypervisor_assist=False,
40
enable_fips=False, remote_access=False):
44
'hypervisorAssist': hypervisor_assist,
45
'type': 'gatewayServices',
46
'enableAesni': enable_aesni,
47
'enableFips': enable_fips,
49
'remoteAccess': remote_access
52
'applianceSize': appliance_size
58
if deployment_container_id:
59
edge['appliances']['deploymentContainerId'] = (
60
deployment_container_id)
62
edge['datacenterMoid'] = datacenter_moid
66
def _assemble_edge_appliance(self, resource_pool_id, datastore_id):
69
appliance['resourcePoolId'] = resource_pool_id
71
appliance['datastoreId'] = datastore_id
74
def _assemble_edge_vnic(self, name, index, portgroup_id,
75
primary_address=None, subnet_mask=None,
78
enable_proxy_arp=False,
79
enable_send_redirects=True,
86
'portgroupId': portgroup_id,
88
'enableProxyArp': enable_proxy_arp,
89
'enableSendRedirects': enable_send_redirects,
90
'isConnected': is_connected
92
if primary_address and subnet_mask:
94
'primaryAddress': primary_address,
95
'subnetMask': subnet_mask
98
address_group['secondaryAddresses'] = {
99
'ipAddress': secondary,
100
'type': 'IpAddressesDto'
103
vnic['addressGroups'] = {
104
'addressGroups': [address_group]
109
def _edge_status_to_level(self, status):
110
if status == 'GREEN':
111
status_level = vcns_const.RouterStatus.ROUTER_STATUS_ACTIVE
112
elif status in ('GREY', 'YELLOW'):
113
status_level = vcns_const.RouterStatus.ROUTER_STATUS_DOWN
115
status_level = vcns_const.RouterStatus.ROUTER_STATUS_ERROR
118
def _enable_loadbalancer(self, edge):
119
if not edge.get('featureConfigs') or (
120
not edge['featureConfigs'].get('features')):
121
edge['featureConfigs'] = {'features': []}
122
edge['featureConfigs']['features'].append(
123
{'featureType': 'loadbalancer_4.0',
126
def get_edge_status(self, edge_id):
128
response = self.vcns.get_edge_status(edge_id)[1]
129
status_level = self._edge_status_to_level(
130
response['edgeStatus'])
131
except exceptions.VcnsApiException as e:
132
LOG.exception(_LE("VCNS: Failed to get edge status:\n%s"),
134
status_level = vcns_const.RouterStatus.ROUTER_STATUS_ERROR
136
desc = jsonutils.loads(e.response)
137
if desc.get('errorCode') == (
138
vcns_const.VCNS_ERROR_CODE_EDGE_NOT_RUNNING):
140
vcns_const.RouterStatus.ROUTER_STATUS_DOWN)
142
LOG.exception(e.response)
146
def get_edges_statuses(self):
147
edges_status_level = {}
148
edges = self._get_edges()
149
for edge in edges['edgePage'].get('data', []):
151
status = edge['edgeStatus']
152
edges_status_level[edge_id] = self._edge_status_to_level(status)
154
return edges_status_level
156
def _update_interface(self, task):
157
edge_id = task.userdata['edge_id']
158
config = task.userdata['config']
159
LOG.debug("VCNS: start updating vnic %s", config)
161
self.vcns.update_interface(edge_id, config)
162
except exceptions.VcnsApiException as e:
163
with excutils.save_and_reraise_exception():
164
LOG.exception(_LE("VCNS: Failed to update vnic %(config)s:\n"
167
'response': e.response})
169
with excutils.save_and_reraise_exception():
170
LOG.exception(_LE("VCNS: Failed to update vnic %d"),
173
return constants.TaskStatus.COMPLETED
175
def update_interface(self, router_id, edge_id, index, network,
176
address=None, netmask=None, secondary=None,
178
LOG.debug("VCNS: update vnic %(index)d: %(addr)s %(netmask)s", {
179
'index': index, 'addr': address, 'netmask': netmask})
180
if index == vcns_const.EXTERNAL_VNIC_INDEX:
181
name = vcns_const.EXTERNAL_VNIC_NAME
183
elif index == vcns_const.INTERNAL_VNIC_INDEX:
184
name = vcns_const.INTERNAL_VNIC_NAME
185
intf_type = 'internal'
187
msg = _("Vnic %d currently not supported") % index
188
raise exceptions.VcnsGeneralException(msg)
190
config = self._assemble_edge_vnic(
191
name, index, network, address, netmask, secondary, type=intf_type)
198
task_name = "update-interface-%s-%d" % (edge_id, index)
199
task = tasks.Task(task_name, router_id,
200
self._update_interface, userdata=userdata)
201
task.add_result_monitor(self.callbacks.interface_update_result)
202
self.task_manager.add(task)
205
def _deploy_edge(self, task):
206
userdata = task.userdata
207
name = userdata['router_name']
208
LOG.debug("VCNS: start deploying edge %s", name)
209
request = userdata['request']
211
header = self.vcns.deploy_edge(request)[0]
212
objuri = header['location']
213
job_id = objuri[objuri.rfind("/") + 1:]
214
response = self.vcns.get_edge_id(job_id)[1]
215
edge_id = response['edgeId']
216
LOG.debug("VCNS: deploying edge %s", edge_id)
217
userdata['edge_id'] = edge_id
218
status = constants.TaskStatus.PENDING
219
except exceptions.VcnsApiException:
220
with excutils.save_and_reraise_exception():
221
LOG.exception(_LE("VCNS: deploy edge failed for router %s."),
226
def _status_edge(self, task):
227
edge_id = task.userdata['edge_id']
229
response = self.vcns.get_edge_deploy_status(edge_id)[1]
230
task.userdata['retries'] = 0
231
system_status = response.get('systemStatus', None)
232
if system_status is None:
233
status = constants.TaskStatus.PENDING
234
elif system_status == 'good':
235
status = constants.TaskStatus.COMPLETED
237
status = constants.TaskStatus.ERROR
238
except exceptions.VcnsApiException:
239
with excutils.save_and_reraise_exception():
240
LOG.exception(_LE("VCNS: Edge %s status query failed."),
243
retries = task.userdata.get('retries', 0) + 1
245
task.userdata['retries'] = retries
246
LOG.exception(_LE("VCNS: Unable to retrieve edge %(edge_id)s "
247
"status. Retry %(retries)d."),
250
status = constants.TaskStatus.PENDING
252
LOG.exception(_LE("VCNS: Unable to retrieve edge %s status. "
254
status = constants.TaskStatus.ERROR
255
LOG.debug("VCNS: Edge %s status", edge_id)
258
def _result_edge(self, task):
259
router_name = task.userdata['router_name']
260
edge_id = task.userdata.get('edge_id')
261
if task.status != constants.TaskStatus.COMPLETED:
262
LOG.error(_LE("VCNS: Failed to deploy edge %(edge_id)s "
263
"for %(name)s, status %(status)d"), {
266
'status': task.status
269
LOG.debug("VCNS: Edge %(edge_id)s deployed for "
271
'edge_id': edge_id, 'name': router_name
274
def _delete_edge(self, task):
275
edge_id = task.userdata['edge_id']
276
LOG.debug("VCNS: start destroying edge %s", edge_id)
277
status = constants.TaskStatus.COMPLETED
280
self.vcns.delete_edge(edge_id)
281
except exceptions.ResourceNotFound:
283
except exceptions.VcnsApiException as e:
284
LOG.exception(_LE("VCNS: Failed to delete %(edge_id)s:\n"
286
{'edge_id': edge_id, 'response': e.response})
287
status = constants.TaskStatus.ERROR
289
LOG.exception(_LE("VCNS: Failed to delete %s"), edge_id)
290
status = constants.TaskStatus.ERROR
294
def _get_edges(self):
296
return self.vcns.get_edges()[1]
297
except exceptions.VcnsApiException as e:
298
with excutils.save_and_reraise_exception():
299
LOG.exception(_LE("VCNS: Failed to get edges:\n%s"),
302
def deploy_edge(self, router_id, name, internal_network, jobdata=None,
303
wait_for_exec=False, loadbalancer_enable=True):
304
task_name = 'deploying-%s' % name
306
edge = self._assemble_edge(
307
edge_name, datacenter_moid=self.datacenter_moid,
308
deployment_container_id=self.deployment_container_id,
309
appliance_size='large', remote_access=True)
310
appliance = self._assemble_edge_appliance(self.resource_pool_id,
313
edge['appliances']['appliances'] = [appliance]
315
vnic_external = self._assemble_edge_vnic(
316
vcns_const.EXTERNAL_VNIC_NAME, vcns_const.EXTERNAL_VNIC_INDEX,
317
self.external_network, type="uplink")
318
edge['vnics']['vnics'].append(vnic_external)
319
vnic_inside = self._assemble_edge_vnic(
320
vcns_const.INTERNAL_VNIC_NAME, vcns_const.INTERNAL_VNIC_INDEX,
322
vcns_const.INTEGRATION_EDGE_IPADDRESS,
323
vcns_const.INTEGRATION_SUBNET_NETMASK,
325
edge['vnics']['vnics'].append(vnic_inside)
326
if loadbalancer_enable:
327
self._enable_loadbalancer(edge)
333
task = tasks.Task(task_name, router_id,
335
status_callback=self._status_edge,
336
result_callback=self._result_edge,
338
task.add_executed_monitor(self.callbacks.edge_deploy_started)
339
task.add_result_monitor(self.callbacks.edge_deploy_result)
340
self.task_manager.add(task)
343
# wait until the deploy task is executed so edge_id is available
344
task.wait(constants.TaskState.EXECUTED)
348
def delete_edge(self, router_id, edge_id, jobdata=None):
349
task_name = 'delete-%s' % edge_id
351
'router_id': router_id,
355
task = tasks.Task(task_name, router_id, self._delete_edge,
357
task.add_result_monitor(self.callbacks.edge_delete_result)
358
self.task_manager.add(task)
361
def _assemble_nat_rule(self, action, original_address,
363
vnic_index=vcns_const.EXTERNAL_VNIC_INDEX,
366
nat_rule['action'] = action
367
nat_rule['vnic'] = vnic_index
368
nat_rule['originalAddress'] = original_address
369
nat_rule['translatedAddress'] = translated_address
370
nat_rule['enabled'] = enabled
373
def get_nat_config(self, edge_id):
375
return self.vcns.get_nat_config(edge_id)[1]
376
except exceptions.VcnsApiException as e:
377
with excutils.save_and_reraise_exception():
378
LOG.exception(_LE("VCNS: Failed to get nat config:\n%s"),
381
def _create_nat_rule(self, task):
382
# TODO(fank): use POST for optimization
383
# return rule_id for future reference
384
rule = task.userdata['rule']
385
LOG.debug("VCNS: start creating nat rules: %s", rule)
386
edge_id = task.userdata['edge_id']
387
nat = self.get_nat_config(edge_id)
388
location = task.userdata['location']
392
if location is None or location == vcns_const.APPEND:
393
nat['rules']['natRulesDtos'].append(rule)
395
nat['rules']['natRulesDtos'].insert(location, rule)
398
self.vcns.update_nat_config(edge_id, nat)
399
status = constants.TaskStatus.COMPLETED
400
except exceptions.VcnsApiException as e:
401
LOG.exception(_LE("VCNS: Failed to create snat rule:\n%s"),
403
status = constants.TaskStatus.ERROR
407
def create_snat_rule(self, router_id, edge_id, src, translated,
408
jobdata=None, location=None):
409
LOG.debug("VCNS: create snat rule %(src)s/%(translated)s", {
410
'src': src, 'translated': translated})
411
snat_rule = self._assemble_nat_rule("snat", src, translated)
413
'router_id': router_id,
416
'location': location,
419
task_name = "create-snat-%s-%s-%s" % (edge_id, src, translated)
420
task = tasks.Task(task_name, router_id, self._create_nat_rule,
422
task.add_result_monitor(self.callbacks.snat_create_result)
423
self.task_manager.add(task)
426
def _delete_nat_rule(self, task):
427
# TODO(fank): pass in rule_id for optimization
428
# handle routes update for optimization
429
edge_id = task.userdata['edge_id']
430
address = task.userdata['address']
431
addrtype = task.userdata['addrtype']
432
LOG.debug("VCNS: start deleting %(type)s rules: %(addr)s", {
433
'type': addrtype, 'addr': address})
434
nat = self.get_nat_config(edge_id)
436
status = constants.TaskStatus.COMPLETED
437
for nat_rule in nat['rules']['natRulesDtos']:
438
if nat_rule[addrtype] == address:
439
rule_id = nat_rule['ruleId']
441
self.vcns.delete_nat_rule(edge_id, rule_id)
442
except exceptions.VcnsApiException as e:
443
LOG.exception(_LE("VCNS: Failed to delete snat rule:\n"
445
status = constants.TaskStatus.ERROR
449
def delete_snat_rule(self, router_id, edge_id, src, jobdata=None):
450
LOG.debug("VCNS: delete snat rule %s", src)
454
'addrtype': 'originalAddress',
457
task_name = "delete-snat-%s-%s" % (edge_id, src)
458
task = tasks.Task(task_name, router_id, self._delete_nat_rule,
460
task.add_result_monitor(self.callbacks.snat_delete_result)
461
self.task_manager.add(task)
464
def create_dnat_rule(self, router_id, edge_id, dst, translated,
465
jobdata=None, location=None):
466
# TODO(fank): use POST for optimization
467
# return rule_id for future reference
468
LOG.debug("VCNS: create dnat rule %(dst)s/%(translated)s", {
469
'dst': dst, 'translated': translated})
470
dnat_rule = self._assemble_nat_rule(
471
"dnat", dst, translated)
473
'router_id': router_id,
476
'location': location,
479
task_name = "create-dnat-%s-%s-%s" % (edge_id, dst, translated)
480
task = tasks.Task(task_name, router_id, self._create_nat_rule,
482
task.add_result_monitor(self.callbacks.dnat_create_result)
483
self.task_manager.add(task)
486
def delete_dnat_rule(self, router_id, edge_id, translated,
488
# TODO(fank): pass in rule_id for optimization
489
LOG.debug("VCNS: delete dnat rule %s", translated)
492
'address': translated,
493
'addrtype': 'translatedAddress',
496
task_name = "delete-dnat-%s-%s" % (edge_id, translated)
497
task = tasks.Task(task_name, router_id, self._delete_nat_rule,
499
task.add_result_monitor(self.callbacks.dnat_delete_result)
500
self.task_manager.add(task)
503
def _update_nat_rule(self, task):
504
# TODO(fank): use POST for optimization
505
# return rule_id for future reference
506
edge_id = task.userdata['edge_id']
507
if task != self.updated_task['nat'][edge_id]:
508
# this task does not have the latest config, abort now
510
return constants.TaskStatus.ABORT
512
rules = task.userdata['rules']
513
LOG.debug("VCNS: start updating nat rules: %s", rules)
516
'featureType': 'nat',
518
'natRulesDtos': rules
523
self.vcns.update_nat_config(edge_id, nat)
524
status = constants.TaskStatus.COMPLETED
525
except exceptions.VcnsApiException as e:
526
LOG.exception(_LE("VCNS: Failed to create snat rule:\n%s"),
528
status = constants.TaskStatus.ERROR
532
def update_nat_rules(self, router_id, edge_id, snats, dnats,
534
LOG.debug("VCNS: update nat rule\n"
537
'snat': snats, 'dnat': dnats})
541
nat_rules.append(self._assemble_nat_rule(
542
'dnat', dnat['dst'], dnat['translated']))
543
nat_rules.append(self._assemble_nat_rule(
544
'snat', dnat['translated'], dnat['dst']))
547
nat_rules.append(self._assemble_nat_rule(
548
'snat', snat['src'], snat['translated']))
555
task_name = "update-nat-%s" % edge_id
556
task = tasks.Task(task_name, router_id, self._update_nat_rule,
558
task.add_result_monitor(self.callbacks.nat_update_result)
559
self.updated_task['nat'][edge_id] = task
560
self.task_manager.add(task)
563
def _update_routes(self, task):
564
edge_id = task.userdata['edge_id']
565
if (task != self.updated_task['route'][edge_id] and
566
task.userdata.get('skippable', True)):
567
# this task does not have the latest config, abort now
569
return constants.TaskStatus.ABORT
570
gateway = task.userdata['gateway']
571
routes = task.userdata['routes']
572
LOG.debug("VCNS: start updating routes for %s", edge_id)
575
static_routes.append({
577
"vnic": vcns_const.INTERNAL_VNIC_INDEX,
578
"network": route['cidr'],
579
"nextHop": route['nexthop']
583
"staticRoutes": static_routes
587
request["defaultRoute"] = {
588
"description": "default-gateway",
589
"gatewayAddress": gateway,
590
"vnic": vcns_const.EXTERNAL_VNIC_INDEX
593
self.vcns.update_routes(edge_id, request)
594
status = constants.TaskStatus.COMPLETED
595
except exceptions.VcnsApiException as e:
596
LOG.exception(_LE("VCNS: Failed to update routes:\n%s"),
598
status = constants.TaskStatus.ERROR
602
def update_routes(self, router_id, edge_id, gateway, routes,
603
skippable=True, jobdata=None):
605
gateway = gateway.split('/')[0]
611
'skippable': skippable,
614
task_name = "update-routes-%s" % (edge_id)
615
task = tasks.Task(task_name, router_id, self._update_routes,
617
task.add_result_monitor(self.callbacks.routes_update_result)
618
self.updated_task['route'][edge_id] = task
619
self.task_manager.add(task)
622
def create_lswitch(self, name, tz_config, tags=None,
623
port_isolation=False, replication_mode="service"):
625
'display_name': utils.check_and_truncate(name),
627
"type": "LogicalSwitchConfig",
628
"_schema": "/ws.v1/schema/LogicalSwitchConfig",
629
"transport_zones": tz_config
631
if port_isolation is bool:
632
lsconfig["port_isolation_enabled"] = port_isolation
634
lsconfig["replication_mode"] = replication_mode
636
response = self.vcns.create_lswitch(lsconfig)[1]
639
def delete_lswitch(self, lswitch_id):
640
self.vcns.delete_lswitch(lswitch_id)
642
def get_loadbalancer_config(self, edge_id):
644
header, response = self.vcns.get_loadbalancer_config(
646
except exceptions.VcnsApiException:
647
with excutils.save_and_reraise_exception():
648
LOG.exception(_LE("Failed to get service config"))
651
def enable_service_loadbalancer(self, edge_id):
652
config = self.get_loadbalancer_config(
654
if not config['enabled']:
655
config['enabled'] = True
657
self.vcns.enable_service_loadbalancer(edge_id, config)
658
except exceptions.VcnsApiException:
659
with excutils.save_and_reraise_exception():
660
LOG.exception(_LE("Failed to enable loadbalancer "