OpenStack Dashboard (Horizon)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

workflows.py 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. # Copyright 2012 NEC Corporation
  2. #
  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
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  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
  13. # under the License.
  14. import logging
  15. from django.conf import settings
  16. from django.core.urlresolvers import reverse
  17. from django.utils.translation import ugettext_lazy as _
  18. import netaddr
  19. from horizon import exceptions
  20. from horizon import forms
  21. from horizon import messages
  22. from horizon import workflows
  23. from openstack_dashboard import api
  24. from openstack_dashboard.dashboards.project.networks.subnets import utils
  25. from openstack_dashboard import policy
  26. LOG = logging.getLogger(__name__)
  27. class CreateNetworkInfoAction(workflows.Action):
  28. net_name = forms.CharField(max_length=255,
  29. label=_("Network Name"),
  30. required=False)
  31. admin_state = forms.BooleanField(
  32. label=_("Enable Admin State"),
  33. initial=True,
  34. required=False,
  35. help_text=_("The state to start the network in."))
  36. shared = forms.BooleanField(label=_("Shared"), initial=False,
  37. required=False)
  38. with_subnet = forms.BooleanField(label=_("Create Subnet"),
  39. widget=forms.CheckboxInput(attrs={
  40. 'class': 'switchable',
  41. 'data-slug': 'with_subnet',
  42. 'data-hide-tab': 'create_network__'
  43. 'createsubnetinfo'
  44. 'action,'
  45. 'create_network__'
  46. 'createsubnetdetail'
  47. 'action',
  48. 'data-hide-on-checked': 'false'
  49. }),
  50. initial=True,
  51. required=False)
  52. az_hints = forms.MultipleChoiceField(
  53. label=_("Availability Zone Hints"),
  54. required=False,
  55. help_text=_("Availability zones where the DHCP agents may be "
  56. "scheduled. Leaving this unset is equivalent to "
  57. "selecting all availability zones"))
  58. def __init__(self, request, *args, **kwargs):
  59. super(CreateNetworkInfoAction, self).__init__(request,
  60. *args, **kwargs)
  61. if not policy.check((("network", "create_network:shared"),), request):
  62. self.fields['shared'].widget = forms.HiddenInput()
  63. try:
  64. if api.neutron.is_extension_supported(request,
  65. 'network_availability_zone'):
  66. zones = api.neutron.list_availability_zones(
  67. self.request, 'network', 'available')
  68. self.fields['az_hints'].choices = [(zone['name'], zone['name'])
  69. for zone in zones]
  70. else:
  71. del self.fields['az_hints']
  72. except Exception:
  73. msg = _('Failed to get availability zone list.')
  74. messages.warning(request, msg)
  75. del self.fields['az_hints']
  76. class Meta(object):
  77. name = _("Network")
  78. help_text = _('Create a new network. '
  79. 'In addition, a subnet associated with the network '
  80. 'can be created in the following steps of this wizard.')
  81. class CreateNetworkInfo(workflows.Step):
  82. action_class = CreateNetworkInfoAction
  83. contributes = ("net_name", "admin_state", "with_subnet", "shared",
  84. "az_hints")
  85. class CreateSubnetInfoAction(workflows.Action):
  86. subnet_name = forms.CharField(max_length=255,
  87. widget=forms.TextInput(attrs={
  88. }),
  89. label=_("Subnet Name"),
  90. required=False)
  91. address_source = forms.ChoiceField(
  92. required=False,
  93. label=_('Network Address Source'),
  94. choices=[('manual', _('Enter Network Address manually')),
  95. ('subnetpool', _('Allocate Network Address from a pool'))],
  96. widget=forms.ThemableSelectWidget(attrs={
  97. 'class': 'switchable',
  98. 'data-slug': 'source',
  99. }))
  100. subnetpool = forms.ChoiceField(
  101. label=_("Address pool"),
  102. widget=forms.ThemableSelectWidget(attrs={
  103. 'class': 'switched switchable',
  104. 'data-slug': 'subnetpool',
  105. 'data-switch-on': 'source',
  106. 'data-source-subnetpool': _('Address pool')},
  107. data_attrs=('name', 'prefixes',
  108. 'ip_version',
  109. 'min_prefixlen',
  110. 'max_prefixlen',
  111. 'default_prefixlen'),
  112. transform=lambda x: "%s (%s)" % (x.name, ", ".join(x.prefixes))
  113. if 'prefixes' in x else "%s" % (x.name)),
  114. required=False)
  115. prefixlen = forms.ChoiceField(widget=forms.ThemableSelectWidget(attrs={
  116. 'class': 'switched',
  117. 'data-switch-on': 'subnetpool',
  118. }),
  119. label=_('Network Mask'),
  120. required=False)
  121. cidr = forms.IPField(label=_("Network Address"),
  122. required=False,
  123. initial="",
  124. widget=forms.TextInput(attrs={
  125. 'class': 'switched',
  126. 'data-switch-on': 'source',
  127. 'data-source-manual': _("Network Address"),
  128. }),
  129. help_text=_("Network address in CIDR format "
  130. "(e.g. 192.168.0.0/24, 2001:DB8::/48)"),
  131. version=forms.IPv4 | forms.IPv6,
  132. mask=True)
  133. ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')],
  134. widget=forms.ThemableSelectWidget(attrs={
  135. 'class': 'switchable',
  136. 'data-slug': 'ipversion',
  137. }),
  138. label=_("IP Version"),
  139. required=False)
  140. gateway_ip = forms.IPField(
  141. label=_("Gateway IP"),
  142. widget=forms.TextInput(attrs={
  143. 'class': 'switched',
  144. 'data-switch-on': 'gateway_ip',
  145. 'data-source-manual': _("Gateway IP")
  146. }),
  147. required=False,
  148. initial="",
  149. help_text=_("IP address of Gateway (e.g. 192.168.0.254) "
  150. "The default value is the first IP of the "
  151. "network address "
  152. "(e.g. 192.168.0.1 for 192.168.0.0/24, "
  153. "2001:DB8::1 for 2001:DB8::/48). "
  154. "If you use the default, leave blank. "
  155. "If you do not want to use a gateway, "
  156. "check 'Disable Gateway' below."),
  157. version=forms.IPv4 | forms.IPv6,
  158. mask=False)
  159. no_gateway = forms.BooleanField(label=_("Disable Gateway"),
  160. widget=forms.CheckboxInput(attrs={
  161. 'class': 'switchable',
  162. 'data-slug': 'gateway_ip',
  163. 'data-hide-on-checked': 'true'
  164. }),
  165. initial=False,
  166. required=False)
  167. check_subnet_range = True
  168. class Meta(object):
  169. name = _("Subnet")
  170. help_text = _('Creates a subnet associated with the network.'
  171. ' You need to enter a valid "Network Address"'
  172. ' and "Gateway IP". If you did not enter the'
  173. ' "Gateway IP", the first value of a network'
  174. ' will be assigned by default. If you do not want'
  175. ' gateway please check the "Disable Gateway" checkbox.'
  176. ' Advanced configuration is available by clicking on'
  177. ' the "Subnet Details" tab.')
  178. def __init__(self, request, context, *args, **kwargs):
  179. super(CreateSubnetInfoAction, self).__init__(request, context, *args,
  180. **kwargs)
  181. if 'with_subnet' in context:
  182. self.fields['with_subnet'] = forms.BooleanField(
  183. initial=context['with_subnet'],
  184. required=False,
  185. widget=forms.HiddenInput()
  186. )
  187. if not getattr(settings, 'OPENSTACK_NEUTRON_NETWORK',
  188. {}).get('enable_ipv6', True):
  189. self.fields['ip_version'].widget = forms.HiddenInput()
  190. self.fields['ip_version'].initial = 4
  191. try:
  192. if api.neutron.is_extension_supported(request,
  193. 'subnet_allocation'):
  194. self.fields['subnetpool'].choices = \
  195. self.get_subnetpool_choices(request)
  196. else:
  197. self.hide_subnetpool_choices()
  198. except Exception:
  199. self.hide_subnetpool_choices()
  200. msg = _('Unable to initialize subnetpools')
  201. exceptions.handle(request, msg)
  202. if len(self.fields['subnetpool'].choices) > 1:
  203. # Pre-populate prefixlen choices to satisfy Django
  204. # ChoiceField Validation. This is overridden w/data from
  205. # subnetpool on select.
  206. self.fields['prefixlen'].choices = \
  207. zip(list(range(0, 128 + 1)),
  208. list(range(0, 128 + 1)))
  209. # Populate data-fields for switching the prefixlen field
  210. # when user selects a subnetpool other than
  211. # "Provider default pool"
  212. for (id, name) in self.fields['subnetpool'].choices:
  213. if not len(id):
  214. continue
  215. key = 'data-subnetpool-' + id
  216. self.fields['prefixlen'].widget.attrs[key] = \
  217. _('Network Mask')
  218. else:
  219. self.hide_subnetpool_choices()
  220. def get_subnetpool_choices(self, request):
  221. subnetpool_choices = [('', _('Select a pool'))]
  222. for subnetpool in api.neutron.subnetpool_list(request):
  223. subnetpool_choices.append((subnetpool.id, subnetpool))
  224. return subnetpool_choices
  225. def hide_subnetpool_choices(self):
  226. self.fields['address_source'].widget = forms.HiddenInput()
  227. self.fields['subnetpool'].choices = []
  228. self.fields['subnetpool'].widget = forms.HiddenInput()
  229. self.fields['prefixlen'].widget = forms.HiddenInput()
  230. def _check_subnet_range(self, subnet, allow_cidr):
  231. allowed_net = netaddr.IPNetwork(allow_cidr)
  232. return subnet in allowed_net
  233. def _check_cidr_allowed(self, ip_version, subnet):
  234. if not self.check_subnet_range:
  235. return
  236. allowed_cidr = getattr(settings, "ALLOWED_PRIVATE_SUBNET_CIDR", {})
  237. version_str = 'ipv%s' % ip_version
  238. allowed_ranges = allowed_cidr.get(version_str, [])
  239. if allowed_ranges:
  240. under_range = any(self._check_subnet_range(subnet, allowed_range)
  241. for allowed_range in allowed_ranges)
  242. if not under_range:
  243. range_str = ', '.join(allowed_ranges)
  244. msg = (_("CIDRs allowed for user private %(ip_ver)s "
  245. "networks are %(allowed)s.") %
  246. {'ip_ver': '%s' % version_str,
  247. 'allowed': range_str})
  248. raise forms.ValidationError(msg)
  249. def _check_subnet_data(self, cleaned_data, is_create=True):
  250. cidr = cleaned_data.get('cidr')
  251. ip_version = int(cleaned_data.get('ip_version'))
  252. gateway_ip = cleaned_data.get('gateway_ip')
  253. no_gateway = cleaned_data.get('no_gateway')
  254. address_source = cleaned_data.get('address_source')
  255. subnetpool = cleaned_data.get('subnetpool')
  256. if not subnetpool and address_source == 'subnetpool':
  257. msg = _('Specify "Address pool" or select '
  258. '"Enter Network Address manually" and specify '
  259. '"Network Address".')
  260. raise forms.ValidationError(msg)
  261. if not cidr and address_source != 'subnetpool':
  262. msg = _('Specify "Network Address" or '
  263. 'clear "Create Subnet" checkbox in previous step.')
  264. raise forms.ValidationError(msg)
  265. if cidr:
  266. subnet = netaddr.IPNetwork(cidr)
  267. if subnet.version != ip_version:
  268. msg = _('Network Address and IP version are inconsistent.')
  269. raise forms.ValidationError(msg)
  270. if (ip_version == 4 and subnet.prefixlen == 32) or \
  271. (ip_version == 6 and subnet.prefixlen == 128):
  272. msg = _("The subnet in the Network Address is "
  273. "too small (/%s).") % subnet.prefixlen
  274. self._errors['cidr'] = self.error_class([msg])
  275. self._check_cidr_allowed(ip_version, subnet)
  276. if not no_gateway and gateway_ip:
  277. if netaddr.IPAddress(gateway_ip).version is not ip_version:
  278. msg = _('Gateway IP and IP version are inconsistent.')
  279. raise forms.ValidationError(msg)
  280. if not is_create and not no_gateway and not gateway_ip:
  281. msg = _('Specify IP address of gateway or '
  282. 'check "Disable Gateway" checkbox.')
  283. raise forms.ValidationError(msg)
  284. def clean(self):
  285. cleaned_data = super(CreateSubnetInfoAction, self).clean()
  286. with_subnet = cleaned_data.get('with_subnet')
  287. if not with_subnet:
  288. return cleaned_data
  289. self._check_subnet_data(cleaned_data)
  290. return cleaned_data
  291. class CreateSubnetInfo(workflows.Step):
  292. action_class = CreateSubnetInfoAction
  293. contributes = ("subnet_name", "cidr", "ip_version",
  294. "gateway_ip", "no_gateway", "subnetpool",
  295. "prefixlen", "address_source")
  296. class CreateSubnetDetailAction(workflows.Action):
  297. enable_dhcp = forms.BooleanField(label=_("Enable DHCP"),
  298. initial=True, required=False)
  299. ipv6_modes = forms.ChoiceField(
  300. label=_("IPv6 Address Configuration Mode"),
  301. widget=forms.ThemableSelectWidget(attrs={
  302. 'class': 'switched',
  303. 'data-switch-on': 'ipversion',
  304. 'data-ipversion-6': _("IPv6 Address Configuration Mode"),
  305. }),
  306. initial=utils.IPV6_DEFAULT_MODE,
  307. required=False,
  308. help_text=_("Specifies how IPv6 addresses and additional information "
  309. "are configured. We can specify SLAAC/DHCPv6 stateful/"
  310. "DHCPv6 stateless provided by OpenStack, "
  311. "or specify no option. "
  312. "'No options specified' means addresses are configured "
  313. "manually or configured by a non-OpenStack system."))
  314. allocation_pools = forms.CharField(
  315. widget=forms.Textarea(attrs={'rows': 4}),
  316. label=_("Allocation Pools"),
  317. help_text=_("IP address allocation pools. Each entry is: "
  318. "start_ip_address,end_ip_address "
  319. "(e.g., 192.168.1.100,192.168.1.120) "
  320. "and one entry per line."),
  321. required=False)
  322. dns_nameservers = forms.CharField(
  323. widget=forms.widgets.Textarea(attrs={'rows': 4}),
  324. label=_("DNS Name Servers"),
  325. help_text=_("IP address list of DNS name servers for this subnet. "
  326. "One entry per line."),
  327. required=False)
  328. host_routes = forms.CharField(
  329. widget=forms.widgets.Textarea(attrs={'rows': 4}),
  330. label=_("Host Routes"),
  331. help_text=_("Additional routes announced to the hosts. "
  332. "Each entry is: destination_cidr,nexthop "
  333. "(e.g., 192.168.200.0/24,10.56.1.254) "
  334. "and one entry per line."),
  335. required=False)
  336. class Meta(object):
  337. name = _("Subnet Details")
  338. help_text = _('Specify additional attributes for the subnet.')
  339. def __init__(self, request, context, *args, **kwargs):
  340. super(CreateSubnetDetailAction, self).__init__(request, context,
  341. *args, **kwargs)
  342. if not getattr(settings, 'OPENSTACK_NEUTRON_NETWORK',
  343. {}).get('enable_ipv6', True):
  344. self.fields['ipv6_modes'].widget = forms.HiddenInput()
  345. def populate_ipv6_modes_choices(self, request, context):
  346. return [(value, _("%s (Default)") % label)
  347. if value == utils.IPV6_DEFAULT_MODE
  348. else (value, label)
  349. for value, label in utils.IPV6_MODE_CHOICES]
  350. def _convert_ip_address(self, ip, field_name):
  351. try:
  352. return netaddr.IPAddress(ip)
  353. except (netaddr.AddrFormatError, ValueError):
  354. msg = (_('%(field_name)s: Invalid IP address (value=%(ip)s)')
  355. % {'field_name': field_name, 'ip': ip})
  356. raise forms.ValidationError(msg)
  357. def _convert_ip_network(self, network, field_name):
  358. try:
  359. return netaddr.IPNetwork(network)
  360. except (netaddr.AddrFormatError, ValueError):
  361. msg = (_('%(field_name)s: Invalid IP address (value=%(network)s)')
  362. % {'field_name': field_name, 'network': network})
  363. raise forms.ValidationError(msg)
  364. def _check_allocation_pools(self, allocation_pools):
  365. for p in allocation_pools.splitlines():
  366. p = p.strip()
  367. if not p:
  368. continue
  369. pool = p.split(',')
  370. if len(pool) != 2:
  371. msg = _('Start and end addresses must be specified '
  372. '(value=%s)') % p
  373. raise forms.ValidationError(msg)
  374. start, end = [self._convert_ip_address(ip, "allocation_pools")
  375. for ip in pool]
  376. if start > end:
  377. msg = _('Start address is larger than end address '
  378. '(value=%s)') % p
  379. raise forms.ValidationError(msg)
  380. def _check_dns_nameservers(self, dns_nameservers):
  381. for ns in dns_nameservers.splitlines():
  382. ns = ns.strip()
  383. if not ns:
  384. continue
  385. self._convert_ip_address(ns, "dns_nameservers")
  386. def _check_host_routes(self, host_routes):
  387. for r in host_routes.splitlines():
  388. r = r.strip()
  389. if not r:
  390. continue
  391. route = r.split(',')
  392. if len(route) != 2:
  393. msg = _('Host Routes format error: '
  394. 'Destination CIDR and nexthop must be specified '
  395. '(value=%s)') % r
  396. raise forms.ValidationError(msg)
  397. self._convert_ip_network(route[0], "host_routes")
  398. self._convert_ip_address(route[1], "host_routes")
  399. def clean(self):
  400. cleaned_data = super(CreateSubnetDetailAction, self).clean()
  401. self._check_allocation_pools(cleaned_data.get('allocation_pools'))
  402. self._check_host_routes(cleaned_data.get('host_routes'))
  403. self._check_dns_nameservers(cleaned_data.get('dns_nameservers'))
  404. return cleaned_data
  405. class CreateSubnetDetail(workflows.Step):
  406. action_class = CreateSubnetDetailAction
  407. contributes = ("enable_dhcp", "ipv6_modes", "allocation_pools",
  408. "dns_nameservers", "host_routes")
  409. class CreateNetwork(workflows.Workflow):
  410. slug = "create_network"
  411. name = _("Create Network")
  412. finalize_button_name = _("Create")
  413. success_message = _('Created network "%s".')
  414. failure_message = _('Unable to create network "%s".')
  415. default_steps = (CreateNetworkInfo,
  416. CreateSubnetInfo,
  417. CreateSubnetDetail)
  418. wizard = True
  419. def get_success_url(self):
  420. return reverse("horizon:project:networks:index")
  421. def get_failure_url(self):
  422. return reverse("horizon:project:networks:index")
  423. def format_status_message(self, message):
  424. name = self.context.get('net_name') or self.context.get('net_id', '')
  425. return message % name
  426. def _create_network(self, request, data):
  427. try:
  428. params = {'name': data['net_name'],
  429. 'admin_state_up': data['admin_state'],
  430. 'shared': data['shared']}
  431. if 'az_hints' in data and data['az_hints']:
  432. params['availability_zone_hints'] = data['az_hints']
  433. network = api.neutron.network_create(request, **params)
  434. self.context['net_id'] = network.id
  435. LOG.debug('Network "%s" was successfully created.',
  436. network.name_or_id)
  437. return network
  438. except Exception as e:
  439. LOG.info('Failed to create network: %s', e)
  440. msg = (_('Failed to create network "%(network)s": %(reason)s') %
  441. {"network": data['net_name'], "reason": e})
  442. redirect = self.get_failure_url()
  443. exceptions.handle(request, msg, redirect=redirect)
  444. return False
  445. def _setup_subnet_parameters(self, params, data, is_create=True):
  446. """Setup subnet parameters
  447. This methods setups subnet parameters which are available
  448. in both create and update.
  449. """
  450. is_update = not is_create
  451. params['enable_dhcp'] = data['enable_dhcp']
  452. if int(data['ip_version']) == 6:
  453. ipv6_modes = utils.get_ipv6_modes_attrs_from_menu(
  454. data['ipv6_modes'])
  455. if ipv6_modes[0] and is_create:
  456. params['ipv6_ra_mode'] = ipv6_modes[0]
  457. if ipv6_modes[1] and is_create:
  458. params['ipv6_address_mode'] = ipv6_modes[1]
  459. if data['allocation_pools']:
  460. pools = [dict(zip(['start', 'end'], pool.strip().split(',')))
  461. for pool in data['allocation_pools'].splitlines()
  462. if pool.strip()]
  463. params['allocation_pools'] = pools
  464. if data['host_routes'] or is_update:
  465. routes = [dict(zip(['destination', 'nexthop'],
  466. route.strip().split(',')))
  467. for route in data['host_routes'].splitlines()
  468. if route.strip()]
  469. params['host_routes'] = routes
  470. if data['dns_nameservers'] or is_update:
  471. nameservers = [ns.strip()
  472. for ns in data['dns_nameservers'].splitlines()
  473. if ns.strip()]
  474. params['dns_nameservers'] = nameservers
  475. def _create_subnet(self, request, data, network=None, tenant_id=None,
  476. no_redirect=False):
  477. if network:
  478. network_id = network.id
  479. network_name = network.name
  480. else:
  481. network_id = self.context.get('network_id')
  482. network_name = self.context.get('network_name')
  483. try:
  484. params = {'network_id': network_id,
  485. 'name': data['subnet_name']}
  486. if 'cidr' in data and data['cidr']:
  487. params['cidr'] = data['cidr']
  488. if 'ip_version' in data and data['ip_version']:
  489. params['ip_version'] = int(data['ip_version'])
  490. if tenant_id:
  491. params['tenant_id'] = tenant_id
  492. if data['no_gateway']:
  493. params['gateway_ip'] = None
  494. elif data['gateway_ip']:
  495. params['gateway_ip'] = data['gateway_ip']
  496. if 'subnetpool' in data and len(data['subnetpool']):
  497. params['subnetpool_id'] = data['subnetpool']
  498. if 'prefixlen' in data and len(data['prefixlen']):
  499. params['prefixlen'] = data['prefixlen']
  500. self._setup_subnet_parameters(params, data)
  501. subnet = api.neutron.subnet_create(request, **params)
  502. self.context['subnet_id'] = subnet.id
  503. LOG.debug('Subnet "%s" was successfully created.', data['cidr'])
  504. return subnet
  505. except Exception as e:
  506. if network_name:
  507. msg = _('Failed to create subnet "%(sub)s" for network '
  508. '"%(net)s": %(reason)s')
  509. else:
  510. msg = _('Failed to create subnet "%(sub)s": %(reason)s')
  511. if no_redirect:
  512. redirect = None
  513. else:
  514. redirect = self.get_failure_url()
  515. exceptions.handle(request,
  516. msg % {"sub": data['cidr'], "net": network_name,
  517. "reason": e},
  518. redirect=redirect)
  519. return False
  520. def _delete_network(self, request, network):
  521. """Delete the created network when subnet creation failed."""
  522. try:
  523. api.neutron.network_delete(request, network.id)
  524. LOG.debug('Delete the created network %s '
  525. 'due to subnet creation failure.', network.id)
  526. msg = _('Delete the created network "%s" '
  527. 'due to subnet creation failure.') % network.name
  528. redirect = self.get_failure_url()
  529. messages.info(request, msg)
  530. raise exceptions.Http302(redirect)
  531. except Exception as e:
  532. LOG.info('Failed to delete network %(id)s: %(exc)s',
  533. {'id': network.id, 'exc': e})
  534. msg = _('Failed to delete network "%s"') % network.name
  535. redirect = self.get_failure_url()
  536. exceptions.handle(request, msg, redirect=redirect)
  537. def handle(self, request, data):
  538. network = self._create_network(request, data)
  539. if not network:
  540. return False
  541. # If we do not need to create a subnet, return here.
  542. if not data['with_subnet']:
  543. return True
  544. subnet = self._create_subnet(request, data, network, no_redirect=True)
  545. if subnet:
  546. return True
  547. else:
  548. self._delete_network(request, network)
  549. return False