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.

forms.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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. from horizon import exceptions
  19. from horizon import forms
  20. from horizon import messages
  21. from openstack_dashboard import api
  22. LOG = logging.getLogger(__name__)
  23. # Predefined provider network types.
  24. # You can add or override these entries by extra_provider_types
  25. # in the settings.
  26. PROVIDER_TYPES = {
  27. 'local': {
  28. 'display_name': _('Local'),
  29. 'require_physical_network': False,
  30. 'require_segmentation_id': False,
  31. },
  32. 'flat': {
  33. 'display_name': _('Flat'),
  34. 'require_physical_network': True,
  35. 'require_segmentation_id': False,
  36. },
  37. 'vlan': {
  38. 'display_name': _('VLAN'),
  39. 'require_physical_network': True,
  40. 'require_segmentation_id': True,
  41. },
  42. 'gre': {
  43. 'display_name': _('GRE'),
  44. 'require_physical_network': False,
  45. 'require_segmentation_id': True,
  46. },
  47. 'vxlan': {
  48. 'display_name': _('VXLAN'),
  49. 'require_physical_network': False,
  50. 'require_segmentation_id': True,
  51. },
  52. 'geneve': {
  53. 'display_name': _('Geneve'),
  54. 'require_physical_network': False,
  55. 'require_segmentation_id': True,
  56. },
  57. 'midonet': {
  58. 'display_name': _('MidoNet'),
  59. 'require_physical_network': False,
  60. 'require_segmentation_id': False,
  61. },
  62. 'uplink': {
  63. 'display_name': _('MidoNet Uplink'),
  64. 'require_physical_network': False,
  65. 'require_segmentation_id': False,
  66. },
  67. }
  68. # Predefined valid segmentation ID range per network type.
  69. # You can add or override these entries by segmentation_id_range
  70. # in the settings.
  71. SEGMENTATION_ID_RANGE = {
  72. 'vlan': (1, 4094),
  73. 'gre': (1, (2 ** 32) - 1),
  74. 'vxlan': (1, (2 ** 24) - 1),
  75. 'geneve': (1, (2 ** 24) - 1),
  76. }
  77. # DEFAULT_PROVIDER_TYPES is used when ['*'] is specified
  78. # in supported_provider_types. This list contains network types
  79. # supported by Neutron ML2 plugin reference implementation.
  80. # You can control enabled network types by
  81. # supported_provider_types setting.
  82. DEFAULT_PROVIDER_TYPES = ['local', 'flat', 'vlan', 'gre', 'vxlan', 'geneve']
  83. class CreateNetwork(forms.SelfHandlingForm):
  84. name = forms.CharField(max_length=255,
  85. label=_("Name"),
  86. required=False)
  87. tenant_id = forms.ThemableChoiceField(label=_("Project"))
  88. network_type = forms.ChoiceField(
  89. label=_("Provider Network Type"),
  90. help_text=_("The physical mechanism by which the virtual "
  91. "network is implemented."),
  92. widget=forms.ThemableSelectWidget(attrs={
  93. 'class': 'switchable',
  94. 'data-slug': 'network_type'
  95. }))
  96. physical_network = forms.CharField(
  97. max_length=255,
  98. label=_("Physical Network"),
  99. help_text=_("The name of the physical network over which the "
  100. "virtual network is implemented. Specify one of the "
  101. "physical networks defined in your neutron deployment."),
  102. widget=forms.TextInput(attrs={
  103. 'class': 'switched',
  104. 'data-switch-on': 'network_type',
  105. }))
  106. segmentation_id = forms.IntegerField(
  107. label=_("Segmentation ID"),
  108. widget=forms.TextInput(attrs={
  109. 'class': 'switched',
  110. 'data-switch-on': 'network_type',
  111. }))
  112. admin_state = forms.BooleanField(label=_("Enable Admin State"),
  113. initial=True,
  114. required=False)
  115. shared = forms.BooleanField(label=_("Shared"),
  116. initial=False, required=False)
  117. external = forms.BooleanField(label=_("External Network"),
  118. initial=False, required=False)
  119. with_subnet = forms.BooleanField(label=_("Create Subnet"),
  120. widget=forms.CheckboxInput(attrs={
  121. 'class': 'switchable',
  122. 'data-slug': 'with_subnet',
  123. 'data-hide-tab': 'create_network__'
  124. 'createsubnetinfo'
  125. 'action,'
  126. 'create_network__'
  127. 'createsubnetdetail'
  128. 'action',
  129. 'data-hide-on-checked': 'false'
  130. }),
  131. initial=True,
  132. required=False)
  133. az_hints = forms.MultipleChoiceField(
  134. label=_("Availability Zone Hints"),
  135. required=False,
  136. help_text=_("Availability zones where the DHCP agents may be "
  137. "scheduled. Leaving this unset is equivalent to "
  138. "selecting all availability zones"))
  139. @classmethod
  140. def _instantiate(cls, request, *args, **kwargs):
  141. return cls(request, *args, **kwargs)
  142. def __init__(self, request, *args, **kwargs):
  143. super(CreateNetwork, self).__init__(request, *args, **kwargs)
  144. tenant_choices = [('', _("Select a project"))]
  145. tenants, has_more = api.keystone.tenant_list(request)
  146. for tenant in tenants:
  147. if tenant.enabled:
  148. tenant_choices.append((tenant.id, tenant.name))
  149. self.fields['tenant_id'].choices = tenant_choices
  150. try:
  151. is_extension_supported = \
  152. api.neutron.is_extension_supported(request, 'provider')
  153. except Exception:
  154. msg = _("Unable to verify Neutron service providers")
  155. exceptions.handle(self.request, msg)
  156. self._hide_provider_network_type()
  157. is_extension_supported = False
  158. if is_extension_supported:
  159. neutron_settings = getattr(settings,
  160. 'OPENSTACK_NEUTRON_NETWORK', {})
  161. self.seg_id_range = SEGMENTATION_ID_RANGE.copy()
  162. seg_id_range = neutron_settings.get('segmentation_id_range')
  163. if seg_id_range:
  164. self.seg_id_range.update(seg_id_range)
  165. self.provider_types = PROVIDER_TYPES.copy()
  166. extra_provider_types = neutron_settings.get('extra_provider_types')
  167. if extra_provider_types:
  168. self.provider_types.update(extra_provider_types)
  169. self.nettypes_with_seg_id = [
  170. net_type for net_type in self.provider_types
  171. if self.provider_types[net_type]['require_segmentation_id']]
  172. self.nettypes_with_physnet = [
  173. net_type for net_type in self.provider_types
  174. if self.provider_types[net_type]['require_physical_network']]
  175. supported_provider_types = neutron_settings.get(
  176. 'supported_provider_types', DEFAULT_PROVIDER_TYPES)
  177. if supported_provider_types == ['*']:
  178. supported_provider_types = DEFAULT_PROVIDER_TYPES
  179. undefined_provider_types = [
  180. net_type for net_type in supported_provider_types
  181. if net_type not in self.provider_types]
  182. if undefined_provider_types:
  183. LOG.error('Undefined provider network types are found: %s',
  184. undefined_provider_types)
  185. seg_id_help = [
  186. _("For %(type)s networks, valid IDs are %(min)s to %(max)s.")
  187. % {'type': net_type,
  188. 'min': self.seg_id_range[net_type][0],
  189. 'max': self.seg_id_range[net_type][1]}
  190. for net_type in self.nettypes_with_seg_id]
  191. self.fields['segmentation_id'].help_text = ' '.join(seg_id_help)
  192. # Register network types which require segmentation ID
  193. attrs = dict(('data-network_type-%s' % network_type,
  194. _('Segmentation ID'))
  195. for network_type in self.nettypes_with_seg_id)
  196. self.fields['segmentation_id'].widget.attrs.update(attrs)
  197. physical_networks = getattr(settings,
  198. 'OPENSTACK_NEUTRON_NETWORK', {}
  199. ).get('physical_networks', [])
  200. if physical_networks:
  201. self.fields['physical_network'] = forms.ThemableChoiceField(
  202. label=_("Physical Network"),
  203. choices=[(net, net) for net in physical_networks],
  204. widget=forms.ThemableSelectWidget(attrs={
  205. 'class': 'switched',
  206. 'data-switch-on': 'network_type',
  207. }),
  208. help_text=_("The name of the physical network over "
  209. "which the virtual network is implemented."),)
  210. # Register network types which require physical network
  211. attrs = dict(('data-network_type-%s' % network_type,
  212. _('Physical Network'))
  213. for network_type in self.nettypes_with_physnet)
  214. self.fields['physical_network'].widget.attrs.update(attrs)
  215. network_type_choices = [
  216. (net_type, self.provider_types[net_type]['display_name'])
  217. for net_type in supported_provider_types]
  218. if len(network_type_choices) == 0:
  219. self._hide_provider_network_type()
  220. else:
  221. self.fields['network_type'].choices = network_type_choices
  222. try:
  223. if api.neutron.is_extension_supported(request,
  224. 'network_availability_zone'):
  225. zones = api.neutron.list_availability_zones(
  226. self.request, 'network', 'available')
  227. self.fields['az_hints'].choices = [(zone['name'], zone['name'])
  228. for zone in zones]
  229. else:
  230. del self.fields['az_hints']
  231. except Exception:
  232. msg = _('Failed to get availability zone list.')
  233. messages.warning(request, msg)
  234. del self.fields['az_hints']
  235. def _hide_provider_network_type(self):
  236. self.fields['network_type'].widget = forms.HiddenInput()
  237. self.fields['physical_network'].widget = forms.HiddenInput()
  238. self.fields['segmentation_id'].widget = forms.HiddenInput()
  239. self.fields['network_type'].required = False
  240. self.fields['physical_network'].required = False
  241. self.fields['segmentation_id'].required = False
  242. def handle(self, request, data):
  243. try:
  244. params = {'name': data['name'],
  245. 'tenant_id': data['tenant_id'],
  246. 'admin_state_up': data['admin_state'],
  247. 'shared': data['shared'],
  248. 'router:external': data['external']}
  249. if api.neutron.is_extension_supported(request, 'provider'):
  250. network_type = data['network_type']
  251. params['provider:network_type'] = network_type
  252. if network_type in self.nettypes_with_physnet:
  253. params['provider:physical_network'] = (
  254. data['physical_network'])
  255. if network_type in self.nettypes_with_seg_id:
  256. params['provider:segmentation_id'] = (
  257. data['segmentation_id'])
  258. if 'az_hints' in data and data['az_hints']:
  259. params['availability_zone_hints'] = data['az_hints']
  260. network = api.neutron.network_create(request, **params)
  261. LOG.debug('Network %s was successfully created.', data['name'])
  262. return network
  263. except Exception:
  264. redirect = reverse('horizon:admin:networks:index')
  265. msg = _('Failed to create network %s') % data['name']
  266. exceptions.handle(request, msg, redirect=redirect)
  267. def clean(self):
  268. cleaned_data = super(CreateNetwork, self).clean()
  269. if api.neutron.is_extension_supported(self.request, 'provider'):
  270. self._clean_physical_network(cleaned_data)
  271. self._clean_segmentation_id(cleaned_data)
  272. return cleaned_data
  273. def _clean_physical_network(self, data):
  274. network_type = data.get('network_type')
  275. if ('physical_network' in self._errors and
  276. network_type not in self.nettypes_with_physnet):
  277. # In this case the physical network is not required, so we can
  278. # ignore any errors.
  279. del self._errors['physical_network']
  280. def _clean_segmentation_id(self, data):
  281. network_type = data.get('network_type')
  282. if 'segmentation_id' in self._errors:
  283. if (network_type not in self.nettypes_with_seg_id and
  284. not self.data.get("segmentation_id")):
  285. # In this case the segmentation ID is not required, so we can
  286. # ignore the field is required error.
  287. del self._errors['segmentation_id']
  288. elif network_type in self.nettypes_with_seg_id:
  289. seg_id = data.get('segmentation_id')
  290. seg_id_range = {'min': self.seg_id_range[network_type][0],
  291. 'max': self.seg_id_range[network_type][1]}
  292. if seg_id < seg_id_range['min'] or seg_id > seg_id_range['max']:
  293. msg = (_('For a %(network_type)s network, valid segmentation '
  294. 'IDs are %(min)s through %(max)s.')
  295. % {'network_type': network_type,
  296. 'min': seg_id_range['min'],
  297. 'max': seg_id_range['max']})
  298. self._errors['segmentation_id'] = self.error_class([msg])
  299. class UpdateNetwork(forms.SelfHandlingForm):
  300. name = forms.CharField(label=_("Name"), required=False)
  301. admin_state = forms.BooleanField(label=_("Enable Admin State"),
  302. required=False)
  303. shared = forms.BooleanField(label=_("Shared"), required=False)
  304. external = forms.BooleanField(label=_("External Network"), required=False)
  305. failure_url = 'horizon:admin:networks:index'
  306. def handle(self, request, data):
  307. try:
  308. params = {'name': data['name'],
  309. 'admin_state_up': data['admin_state'],
  310. 'shared': data['shared'],
  311. 'router:external': data['external']}
  312. network = api.neutron.network_update(request,
  313. self.initial['network_id'],
  314. **params)
  315. msg = _('Network %s was successfully updated.') % data['name']
  316. messages.success(request, msg)
  317. return network
  318. except Exception as e:
  319. LOG.info('Failed to update network %(id)s: %(exc)s',
  320. {'id': self.initial['network_id'], 'exc': e})
  321. msg = _('Failed to update network %s') % data['name']
  322. redirect = reverse(self.failure_url)
  323. exceptions.handle(request, msg, redirect=redirect)