1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5#    http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10# See the License for the specific language governing permissions and
11# limitations under the License.
12
13# import types so that we can reference ListType in sphinx param declarations.
14# We can't just use list, because sphinx gets confused by
15# openstack.resource.Resource.list and openstack.resource2.Resource.list
16import threading
17import time
18import types  # noqa
19
20from openstack.cloud import _normalize
21from openstack.cloud import _utils
22from openstack.cloud import exc
23from openstack import exceptions
24from openstack import proxy
25
26
27class NetworkCloudMixin(_normalize.Normalizer):
28
29    def __init__(self):
30        self._ports = None
31        self._ports_time = 0
32        self._ports_lock = threading.Lock()
33
34    @_utils.cache_on_arguments()
35    def _neutron_extensions(self):
36        extensions = set()
37        for extension in self.network.extensions():
38            extensions.add(extension['alias'])
39        return extensions
40
41    def _has_neutron_extension(self, extension_alias):
42        return extension_alias in self._neutron_extensions()
43
44    def search_networks(self, name_or_id=None, filters=None):
45        """Search networks
46
47        :param name_or_id: Name or ID of the desired network.
48        :param filters: a dict containing additional filters to use. e.g.
49                        {'router:external': True}
50
51        :returns: a list of ``munch.Munch`` containing the network description.
52
53        :raises: ``OpenStackCloudException`` if something goes wrong during the
54            OpenStack API call.
55        """
56        networks = self.list_networks(
57            filters if isinstance(filters, dict) else None)
58        return _utils._filter_list(networks, name_or_id, filters)
59
60    def search_routers(self, name_or_id=None, filters=None):
61        """Search routers
62
63        :param name_or_id: Name or ID of the desired router.
64        :param filters: a dict containing additional filters to use. e.g.
65                        {'admin_state_up': True}
66
67        :returns: a list of ``munch.Munch`` containing the router description.
68
69        :raises: ``OpenStackCloudException`` if something goes wrong during the
70            OpenStack API call.
71        """
72        routers = self.list_routers(
73            filters if isinstance(filters, dict) else None)
74        return _utils._filter_list(routers, name_or_id, filters)
75
76    def search_subnets(self, name_or_id=None, filters=None):
77        """Search subnets
78
79        :param name_or_id: Name or ID of the desired subnet.
80        :param filters: a dict containing additional filters to use. e.g.
81                        {'enable_dhcp': True}
82
83        :returns: a list of ``munch.Munch`` containing the subnet description.
84
85        :raises: ``OpenStackCloudException`` if something goes wrong during the
86            OpenStack API call.
87        """
88        subnets = self.list_subnets(
89            filters if isinstance(filters, dict) else None)
90        return _utils._filter_list(subnets, name_or_id, filters)
91
92    def search_ports(self, name_or_id=None, filters=None):
93        """Search ports
94
95        :param name_or_id: Name or ID of the desired port.
96        :param filters: a dict containing additional filters to use. e.g.
97                        {'device_id': '2711c67a-b4a7-43dd-ace7-6187b791c3f0'}
98
99        :returns: a list of ``munch.Munch`` containing the port description.
100
101        :raises: ``OpenStackCloudException`` if something goes wrong during the
102            OpenStack API call.
103        """
104        # If port caching is enabled, do not push the filter down to
105        # neutron; get all the ports (potentially from the cache) and
106        # filter locally.
107        if self._PORT_AGE or isinstance(filters, str):
108            pushdown_filters = None
109        else:
110            pushdown_filters = filters
111        ports = self.list_ports(pushdown_filters)
112        return _utils._filter_list(ports, name_or_id, filters)
113
114    def list_networks(self, filters=None):
115        """List all available networks.
116
117        :param filters: (optional) dict of filter conditions to push down
118        :returns: A list of ``munch.Munch`` containing network info.
119
120        """
121        # If the cloud is running nova-network, just return an empty list.
122        if not self.has_service('network'):
123            return []
124        # Translate None from search interface to empty {} for kwargs below
125        if not filters:
126            filters = {}
127        data = self.network.get("/networks", params=filters)
128        return self._get_and_munchify('networks', data)
129
130    def list_routers(self, filters=None):
131        """List all available routers.
132
133        :param filters: (optional) dict of filter conditions to push down
134        :returns: A list of router ``munch.Munch``.
135
136        """
137        # If the cloud is running nova-network, just return an empty list.
138        if not self.has_service('network'):
139            return []
140        # Translate None from search interface to empty {} for kwargs below
141        if not filters:
142            filters = {}
143        resp = self.network.get("/routers", params=filters)
144        data = proxy._json_response(
145            resp,
146            error_message="Error fetching router list")
147        return self._get_and_munchify('routers', data)
148
149    def list_subnets(self, filters=None):
150        """List all available subnets.
151
152        :param filters: (optional) dict of filter conditions to push down
153        :returns: A list of subnet ``munch.Munch``.
154
155        """
156        # If the cloud is running nova-network, just return an empty list.
157        if not self.has_service('network'):
158            return []
159        # Translate None from search interface to empty {} for kwargs below
160        if not filters:
161            filters = {}
162        data = self.network.get("/subnets", params=filters)
163        return self._get_and_munchify('subnets', data)
164
165    def list_ports(self, filters=None):
166        """List all available ports.
167
168        :param filters: (optional) dict of filter conditions to push down
169        :returns: A list of port ``munch.Munch``.
170
171        """
172        # If pushdown filters are specified and we do not have batched caching
173        # enabled, bypass local caching and push down the filters.
174        if filters and self._PORT_AGE == 0:
175            return self._list_ports(filters)
176
177        if (time.time() - self._ports_time) >= self._PORT_AGE:
178            # Since we're using cached data anyway, we don't need to
179            # have more than one thread actually submit the list
180            # ports task.  Let the first one submit it while holding
181            # a lock, and the non-blocking acquire method will cause
182            # subsequent threads to just skip this and use the old
183            # data until it succeeds.
184            # Initially when we never got data, block to retrieve some data.
185            first_run = self._ports is None
186            if self._ports_lock.acquire(first_run):
187                try:
188                    if not (first_run and self._ports is not None):
189                        self._ports = self._list_ports({})
190                        self._ports_time = time.time()
191                finally:
192                    self._ports_lock.release()
193        # Wrap the return with filter_list so that if filters were passed
194        # but we were batching/caching and thus always fetching the whole
195        # list from the cloud, we still return a filtered list.
196        return _utils._filter_list(self._ports, None, filters or {})
197
198    def _list_ports(self, filters):
199        # If the cloud is running nova-network, just return an empty list.
200        if not self.has_service('network'):
201            return []
202        resp = self.network.get("/ports", params=filters)
203        data = proxy._json_response(
204            resp,
205            error_message="Error fetching port list")
206        return self._get_and_munchify('ports', data)
207
208    def get_qos_policy(self, name_or_id, filters=None):
209        """Get a QoS policy by name or ID.
210
211        :param name_or_id: Name or ID of the policy.
212        :param filters:
213            A dictionary of meta data to use for further filtering. Elements
214            of this dictionary may, themselves, be dictionaries. Example::
215
216                {
217                  'last_name': 'Smith',
218                  'other': {
219                      'gender': 'Female'
220                  }
221                }
222
223            OR
224            A string containing a jmespath expression for further filtering.
225            Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"
226
227        :returns: A policy ``munch.Munch`` or None if no matching network is
228                 found.
229
230        """
231        if not self._has_neutron_extension('qos'):
232            raise exc.OpenStackCloudUnavailableExtension(
233                'QoS extension is not available on target cloud')
234        if not filters:
235            filters = {}
236        return self.network.find_qos_policy(
237            name_or_id=name_or_id,
238            ignore_missing=True,
239            **filters)
240
241    def search_qos_policies(self, name_or_id=None, filters=None):
242        """Search QoS policies
243
244        :param name_or_id: Name or ID of the desired policy.
245        :param filters: a dict containing additional filters to use. e.g.
246                        {'shared': True}
247
248        :returns: a list of ``munch.Munch`` containing the network description.
249
250        :raises: ``OpenStackCloudException`` if something goes wrong during the
251            OpenStack API call.
252        """
253        if not self._has_neutron_extension('qos'):
254            raise exc.OpenStackCloudUnavailableExtension(
255                'QoS extension is not available on target cloud')
256        query = {}
257        if name_or_id:
258            query['name'] = name_or_id
259        if filters:
260            query.update(filters)
261        return list(self.network.qos_policies(**query))
262
263    def list_qos_rule_types(self, filters=None):
264        """List all available QoS rule types.
265
266        :param filters: (optional) dict of filter conditions to push down
267        :returns: A list of rule types ``munch.Munch``.
268
269        """
270        if not self._has_neutron_extension('qos'):
271            raise exc.OpenStackCloudUnavailableExtension(
272                'QoS extension is not available on target cloud')
273
274        # Translate None from search interface to empty {} for kwargs below
275        if not filters:
276            filters = {}
277        return list(self.network.qos_rule_types(**filters))
278
279    def get_qos_rule_type_details(self, rule_type, filters=None):
280        """Get a QoS rule type details by rule type name.
281
282        :param string rule_type: Name of the QoS rule type.
283
284        :returns: A rule type details ``munch.Munch`` or None if
285            no matching rule type is found.
286
287        """
288        if not self._has_neutron_extension('qos'):
289            raise exc.OpenStackCloudUnavailableExtension(
290                'QoS extension is not available on target cloud')
291
292        if not self._has_neutron_extension('qos-rule-type-details'):
293            raise exc.OpenStackCloudUnavailableExtension(
294                'qos-rule-type-details extension is not available '
295                'on target cloud')
296
297        return self.network.get_qos_rule_type(rule_type)
298
299    def list_qos_policies(self, filters=None):
300        """List all available QoS policies.
301
302        :param filters: (optional) dict of filter conditions to push down
303        :returns: A list of policies ``munch.Munch``.
304
305        """
306        if not self._has_neutron_extension('qos'):
307            raise exc.OpenStackCloudUnavailableExtension(
308                'QoS extension is not available on target cloud')
309        # Translate None from search interface to empty {} for kwargs below
310        if not filters:
311            filters = {}
312        return list(self.network.qos_policies(**filters))
313
314    def get_network(self, name_or_id, filters=None):
315        """Get a network by name or ID.
316
317        :param name_or_id: Name or ID of the network.
318        :param filters:
319            A dictionary of meta data to use for further filtering. Elements
320            of this dictionary may, themselves, be dictionaries. Example::
321
322                {
323                  'last_name': 'Smith',
324                  'other': {
325                      'gender': 'Female'
326                  }
327                }
328
329            OR
330            A string containing a jmespath expression for further filtering.
331            Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"
332
333        :returns: A network ``munch.Munch`` or None if no matching network is
334                 found.
335
336        """
337        return _utils._get_entity(self, 'network', name_or_id, filters)
338
339    def get_network_by_id(self, id):
340        """ Get a network by ID
341
342        :param id: ID of the network.
343        :returns: A network ``munch.Munch``.
344        """
345        resp = self.network.get('/networks/{id}'.format(id=id))
346        data = proxy._json_response(
347            resp,
348            error_message="Error getting network with ID {id}".format(id=id)
349        )
350        network = self._get_and_munchify('network', data)
351
352        return network
353
354    def get_router(self, name_or_id, filters=None):
355        """Get a router by name or ID.
356
357        :param name_or_id: Name or ID of the router.
358        :param filters:
359            A dictionary of meta data to use for further filtering. Elements
360            of this dictionary may, themselves, be dictionaries. Example::
361
362                {
363                  'last_name': 'Smith',
364                  'other': {
365                      'gender': 'Female'
366                  }
367                }
368
369            OR
370            A string containing a jmespath expression for further filtering.
371            Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"
372
373        :returns: A router ``munch.Munch`` or None if no matching router is
374                  found.
375
376        """
377        return _utils._get_entity(self, 'router', name_or_id, filters)
378
379    def get_subnet(self, name_or_id, filters=None):
380        """Get a subnet by name or ID.
381
382        :param name_or_id: Name or ID of the subnet.
383        :param filters:
384            A dictionary of meta data to use for further filtering. Elements
385            of this dictionary may, themselves, be dictionaries. Example::
386
387                {
388                  'last_name': 'Smith',
389                  'other': {
390                      'gender': 'Female'
391                  }
392                }
393
394        :returns: A subnet ``munch.Munch`` or None if no matching subnet is
395                  found.
396
397        """
398        return _utils._get_entity(self, 'subnet', name_or_id, filters)
399
400    def get_subnet_by_id(self, id):
401        """ Get a subnet by ID
402
403        :param id: ID of the subnet.
404        :returns: A subnet ``munch.Munch``.
405        """
406        resp = self.network.get('/subnets/{id}'.format(id=id))
407        data = proxy._json_response(
408            resp,
409            error_message="Error getting subnet with ID {id}".format(id=id)
410        )
411        subnet = self._get_and_munchify('subnet', data)
412
413        return subnet
414
415    def get_port(self, name_or_id, filters=None):
416        """Get a port by name or ID.
417
418        :param name_or_id: Name or ID of the port.
419        :param filters:
420            A dictionary of meta data to use for further filtering. Elements
421            of this dictionary may, themselves, be dictionaries. Example::
422
423                {
424                  'last_name': 'Smith',
425                  'other': {
426                      'gender': 'Female'
427                  }
428                }
429
430            OR
431            A string containing a jmespath expression for further filtering.
432            Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"
433
434        :returns: A port ``munch.Munch`` or None if no matching port is found.
435
436        """
437        return _utils._get_entity(self, 'port', name_or_id, filters)
438
439    def get_port_by_id(self, id):
440        """ Get a port by ID
441
442        :param id: ID of the port.
443        :returns: A port ``munch.Munch``.
444        """
445        resp = self.network.get('/ports/{id}'.format(id=id))
446        data = proxy._json_response(
447            resp,
448            error_message="Error getting port with ID {id}".format(id=id)
449        )
450        port = self._get_and_munchify('port', data)
451
452        return port
453
454    def create_network(self, name, shared=False, admin_state_up=True,
455                       external=False, provider=None, project_id=None,
456                       availability_zone_hints=None,
457                       port_security_enabled=None,
458                       mtu_size=None, dns_domain=None):
459        """Create a network.
460
461        :param string name: Name of the network being created.
462        :param bool shared: Set the network as shared.
463        :param bool admin_state_up: Set the network administrative state to up.
464        :param bool external: Whether this network is externally accessible.
465        :param dict provider: A dict of network provider options. Example::
466
467           { 'network_type': 'vlan', 'segmentation_id': 'vlan1' }
468        :param string project_id: Specify the project ID this network
469            will be created on (admin-only).
470        :param types.ListType availability_zone_hints: A list of availability
471            zone hints.
472        :param bool port_security_enabled: Enable / Disable port security
473        :param int mtu_size: maximum transmission unit value to address
474            fragmentation. Minimum value is 68 for IPv4, and 1280 for IPv6.
475        :param string dns_domain: Specify the DNS domain associated with
476            this network.
477
478        :returns: The network object.
479        :raises: OpenStackCloudException on operation error.
480        """
481        network = {
482            'name': name,
483            'admin_state_up': admin_state_up,
484        }
485
486        if shared:
487            network['shared'] = shared
488
489        if project_id is not None:
490            network['tenant_id'] = project_id
491
492        if availability_zone_hints is not None:
493            if not isinstance(availability_zone_hints, list):
494                raise exc.OpenStackCloudException(
495                    "Parameter 'availability_zone_hints' must be a list")
496            if not self._has_neutron_extension('network_availability_zone'):
497                raise exc.OpenStackCloudUnavailableExtension(
498                    'network_availability_zone extension is not available on '
499                    'target cloud')
500            network['availability_zone_hints'] = availability_zone_hints
501
502        if provider:
503            if not isinstance(provider, dict):
504                raise exc.OpenStackCloudException(
505                    "Parameter 'provider' must be a dict")
506            # Only pass what we know
507            for attr in ('physical_network', 'network_type',
508                         'segmentation_id'):
509                if attr in provider:
510                    arg = "provider:" + attr
511                    network[arg] = provider[attr]
512
513        # Do not send 'router:external' unless it is explicitly
514        # set since sending it *might* cause "Forbidden" errors in
515        # some situations. It defaults to False in the client, anyway.
516        if external:
517            network['router:external'] = True
518
519        if port_security_enabled is not None:
520            if not isinstance(port_security_enabled, bool):
521                raise exc.OpenStackCloudException(
522                    "Parameter 'port_security_enabled' must be a bool")
523            network['port_security_enabled'] = port_security_enabled
524
525        if mtu_size:
526            if not isinstance(mtu_size, int):
527                raise exc.OpenStackCloudException(
528                    "Parameter 'mtu_size' must be an integer.")
529            if not mtu_size >= 68:
530                raise exc.OpenStackCloudException(
531                    "Parameter 'mtu_size' must be greater than 67.")
532
533            network['mtu'] = mtu_size
534
535        if dns_domain:
536            network['dns_domain'] = dns_domain
537
538        data = self.network.post("/networks", json={'network': network})
539
540        # Reset cache so the new network is picked up
541        self._reset_network_caches()
542        return self._get_and_munchify('network', data)
543
544    @_utils.valid_kwargs("name", "shared", "admin_state_up", "external",
545                         "provider", "mtu_size", "port_security_enabled",
546                         "dns_domain")
547    def update_network(self, name_or_id, **kwargs):
548        """Update a network.
549
550        :param string name_or_id: Name or ID of the network being updated.
551        :param string name: New name of the network.
552        :param bool shared: Set the network as shared.
553        :param bool admin_state_up: Set the network administrative state to up.
554        :param bool external: Whether this network is externally accessible.
555        :param dict provider: A dict of network provider options. Example::
556
557           { 'network_type': 'vlan', 'segmentation_id': 'vlan1' }
558        :param int mtu_size: New maximum transmission unit value to address
559            fragmentation. Minimum value is 68 for IPv4, and 1280 for IPv6.
560        :param bool port_security_enabled: Enable or disable port security.
561        :param string dns_domain: Specify the DNS domain associated with
562            this network.
563
564        :returns: The updated network object.
565        :raises: OpenStackCloudException on operation error.
566        """
567        if 'provider' in kwargs:
568            if not isinstance(kwargs['provider'], dict):
569                raise exc.OpenStackCloudException(
570                    "Parameter 'provider' must be a dict")
571            # Only pass what we know
572            provider = {}
573            for key in kwargs['provider']:
574                if key in ('physical_network', 'network_type',
575                           'segmentation_id'):
576                    provider['provider:' + key] = kwargs['provider'][key]
577            kwargs['provider'] = provider
578
579        if 'external' in kwargs:
580            kwargs['router:external'] = kwargs.pop('external')
581
582        if 'port_security_enabled' in kwargs:
583            if not isinstance(kwargs['port_security_enabled'], bool):
584                raise exc.OpenStackCloudException(
585                    "Parameter 'port_security_enabled' must be a bool")
586
587        if 'mtu_size' in kwargs:
588            if not isinstance(kwargs['mtu_size'], int):
589                raise exc.OpenStackCloudException(
590                    "Parameter 'mtu_size' must be an integer.")
591            if kwargs['mtu_size'] < 68:
592                raise exc.OpenStackCloudException(
593                    "Parameter 'mtu_size' must be greater than 67.")
594            kwargs['mtu'] = kwargs.pop('mtu_size')
595
596        network = self.get_network(name_or_id)
597        if not network:
598            raise exc.OpenStackCloudException(
599                "Network %s not found." % name_or_id)
600
601        data = proxy._json_response(self.network.put(
602            "/networks/{net_id}".format(net_id=network.id),
603            json={"network": kwargs}),
604            error_message="Error updating network {0}".format(name_or_id))
605
606        self._reset_network_caches()
607
608        return self._get_and_munchify('network', data)
609
610    def delete_network(self, name_or_id):
611        """Delete a network.
612
613        :param name_or_id: Name or ID of the network being deleted.
614
615        :returns: True if delete succeeded, False otherwise.
616
617        :raises: OpenStackCloudException on operation error.
618        """
619        network = self.get_network(name_or_id)
620        if not network:
621            self.log.debug("Network %s not found for deleting", name_or_id)
622            return False
623
624        exceptions.raise_from_response(self.network.delete(
625            "/networks/{network_id}".format(network_id=network['id'])))
626
627        # Reset cache so the deleted network is removed
628        self._reset_network_caches()
629
630        return True
631
632    def set_network_quotas(self, name_or_id, **kwargs):
633        """ Set a network quota in a project
634
635        :param name_or_id: project name or id
636        :param kwargs: key/value pairs of quota name and quota value
637
638        :raises: OpenStackCloudException if the resource to set the
639            quota does not exist.
640        """
641
642        proj = self.get_project(name_or_id)
643        if not proj:
644            raise exc.OpenStackCloudException("project does not exist")
645
646        exceptions.raise_from_response(
647            self.network.put(
648                '/quotas/{project_id}'.format(project_id=proj.id),
649                json={'quota': kwargs}),
650            error_message=("Error setting Neutron's quota for "
651                           "project {0}".format(proj.id)))
652
653    def get_network_quotas(self, name_or_id, details=False):
654        """ Get network quotas for a project
655
656        :param name_or_id: project name or id
657        :param details: if set to True it will return details about usage
658                        of quotas by given project
659        :raises: OpenStackCloudException if it's not a valid project
660
661        :returns: Munch object with the quotas
662        """
663        proj = self.get_project(name_or_id)
664        if not proj:
665            raise exc.OpenStackCloudException("project does not exist")
666        url = '/quotas/{project_id}'.format(project_id=proj.id)
667        if details:
668            url = url + "/details"
669        data = proxy._json_response(
670            self.network.get(url),
671            error_message=("Error fetching Neutron's quota for "
672                           "project {0}".format(proj.id)))
673        return self._get_and_munchify('quota', data)
674
675    def get_network_extensions(self):
676        """Get Cloud provided network extensions
677
678        :returns: set of Neutron extension aliases
679        """
680        return self._neutron_extensions()
681
682    def delete_network_quotas(self, name_or_id):
683        """ Delete network quotas for a project
684
685        :param name_or_id: project name or id
686        :raises: OpenStackCloudException if it's not a valid project or the
687                 network client call failed
688
689        :returns: dict with the quotas
690        """
691        proj = self.get_project(name_or_id)
692        if not proj:
693            raise exc.OpenStackCloudException("project does not exist")
694        exceptions.raise_from_response(
695            self.network.delete(
696                '/quotas/{project_id}'.format(project_id=proj.id)),
697            error_message=("Error deleting Neutron's quota for "
698                           "project {0}".format(proj.id)))
699
700    @_utils.valid_kwargs(
701        'action', 'description', 'destination_firewall_group_id',
702        'destination_ip_address', 'destination_port', 'enabled', 'ip_version',
703        'name', 'project_id', 'protocol', 'shared', 'source_firewall_group_id',
704        'source_ip_address', 'source_port')
705    def create_firewall_rule(self, **kwargs):
706        """
707        Creates firewall rule.
708
709        :param action: Action performed on traffic.
710            Valid values: allow, deny
711            Defaults to deny.
712        :param description: Human-readable description.
713        :param destination_firewall_group_id: ID of destination firewall group.
714        :param destination_ip_address: IPv4-, IPv6 address or CIDR.
715        :param destination_port: Port or port range (e.g. 80:90)
716        :param bool enabled: Status of firewall rule. You can disable rules
717                             without disassociating them from firewall
718                             policies. Defaults to True.
719        :param int ip_version: IP Version.
720                           Valid values: 4, 6
721                           Defaults to 4.
722        :param name: Human-readable name.
723        :param project_id: Project id.
724        :param protocol: IP protocol.
725                         Valid values: icmp, tcp, udp, null
726        :param bool shared: Visibility to other projects.
727                       Defaults to False.
728        :param source_firewall_group_id: ID of source firewall group.
729        :param source_ip_address: IPv4-, IPv6 address or CIDR.
730        :param source_port: Port or port range (e.g. 80:90)
731        :raises: BadRequestException if parameters are malformed
732        :return: created firewall rule
733        :rtype: FirewallRule
734        """
735        return self.network.create_firewall_rule(**kwargs)
736
737    def delete_firewall_rule(self, name_or_id, filters=None):
738        """
739        Deletes firewall rule.
740        Prints debug message in case to-be-deleted resource was not found.
741
742        :param name_or_id: firewall rule name or id
743        :param dict filters: optional filters
744        :raises: DuplicateResource on multiple matches
745        :return: True if resource is successfully deleted, False otherwise.
746        :rtype: bool
747        """
748        if not filters:
749            filters = {}
750        try:
751            firewall_rule = self.network.find_firewall_rule(
752                name_or_id, ignore_missing=False, **filters)
753            self.network.delete_firewall_rule(firewall_rule,
754                                              ignore_missing=False)
755        except exceptions.ResourceNotFound:
756            self.log.debug('Firewall rule %s not found for deleting',
757                           name_or_id)
758            return False
759        return True
760
761    def get_firewall_rule(self, name_or_id, filters=None):
762        """
763        Retrieves a single firewall rule.
764
765        :param name_or_id: firewall rule name or id
766        :param dict filters: optional filters
767        :raises: DuplicateResource on multiple matches
768        :return: firewall rule dict or None if not found
769        :rtype: FirewallRule
770        """
771        if not filters:
772            filters = {}
773        return self.network.find_firewall_rule(name_or_id, **filters)
774
775    def list_firewall_rules(self, filters=None):
776        """
777        Lists firewall rules.
778
779        :param dict filters: optional filters
780        :return: list of firewall rules
781        :rtype: list[FirewallRule]
782        """
783        if not filters:
784            filters = {}
785        return list(self.network.firewall_rules(**filters))
786
787    @_utils.valid_kwargs(
788        'action', 'description', 'destination_firewall_group_id',
789        'destination_ip_address', 'destination_port', 'enabled', 'ip_version',
790        'name', 'project_id', 'protocol', 'shared', 'source_firewall_group_id',
791        'source_ip_address', 'source_port')
792    def update_firewall_rule(self, name_or_id, filters=None, **kwargs):
793        """
794        Updates firewall rule.
795
796        :param name_or_id: firewall rule name or id
797        :param dict filters: optional filters
798        :param kwargs: firewall rule update parameters.
799            See create_firewall_rule docstring for valid parameters.
800        :raises: BadRequestException if parameters are malformed
801        :raises: NotFoundException if resource is not found
802        :return: updated firewall rule
803        :rtype: FirewallRule
804        """
805        if not filters:
806            filters = {}
807        firewall_rule = self.network.find_firewall_rule(
808            name_or_id, ignore_missing=False, **filters)
809
810        return self.network.update_firewall_rule(firewall_rule, **kwargs)
811
812    def _get_firewall_rule_ids(self, name_or_id_list, filters=None):
813        """
814        Takes a list of firewall rule name or ids, looks them up and returns
815        a list of firewall rule ids.
816
817        Used by `create_firewall_policy` and `update_firewall_policy`.
818
819        :param list[str] name_or_id_list: firewall rule name or id list
820        :param dict filters: optional filters
821        :raises: DuplicateResource on multiple matches
822        :raises: NotFoundException if resource is not found
823        :return: list of firewall rule ids
824        :rtype: list[str]
825        """
826        if not filters:
827            filters = {}
828        ids_list = []
829        for name_or_id in name_or_id_list:
830            ids_list.append(self.network.find_firewall_rule(
831                name_or_id, ignore_missing=False, **filters)['id'])
832        return ids_list
833
834    @_utils.valid_kwargs('audited', 'description', 'firewall_rules', 'name',
835                         'project_id', 'shared')
836    def create_firewall_policy(self, **kwargs):
837        """
838        Create firewall policy.
839
840        :param bool audited: Status of audition of firewall policy.
841                             Set to False each time the firewall policy or the
842                             associated firewall rules are changed.
843                             Has to be explicitly set to True.
844        :param description: Human-readable description.
845        :param list[str] firewall_rules: List of associated firewall rules.
846        :param name: Human-readable name.
847        :param project_id: Project id.
848        :param bool shared: Visibility to other projects.
849                       Defaults to False.
850        :raises: BadRequestException if parameters are malformed
851        :raises: ResourceNotFound if a resource from firewall_list not found
852        :return: created firewall policy
853        :rtype: FirewallPolicy
854        """
855        if 'firewall_rules' in kwargs:
856            kwargs['firewall_rules'] = self._get_firewall_rule_ids(
857                kwargs['firewall_rules'])
858
859        return self.network.create_firewall_policy(**kwargs)
860
861    def delete_firewall_policy(self, name_or_id, filters=None):
862        """
863        Deletes firewall policy.
864        Prints debug message in case to-be-deleted resource was not found.
865
866        :param name_or_id: firewall policy name or id
867        :param dict filters: optional filters
868        :raises: DuplicateResource on multiple matches
869        :return: True if resource is successfully deleted, False otherwise.
870        :rtype: bool
871        """
872        if not filters:
873            filters = {}
874        try:
875            firewall_policy = self.network.find_firewall_policy(
876                name_or_id, ignore_missing=False, **filters)
877            self.network.delete_firewall_policy(firewall_policy,
878                                                ignore_missing=False)
879        except exceptions.ResourceNotFound:
880            self.log.debug('Firewall policy %s not found for deleting',
881                           name_or_id)
882            return False
883        return True
884
885    def get_firewall_policy(self, name_or_id, filters=None):
886        """
887        Retrieves a single firewall policy.
888
889        :param name_or_id: firewall policy name or id
890        :param dict filters: optional filters
891        :raises: DuplicateResource on multiple matches
892        :return: firewall policy or None if not found
893        :rtype: FirewallPolicy
894        """
895        if not filters:
896            filters = {}
897        return self.network.find_firewall_policy(name_or_id, **filters)
898
899    def list_firewall_policies(self, filters=None):
900        """
901        Lists firewall policies.
902
903        :param dict filters: optional filters
904        :return: list of firewall policies
905        :rtype: list[FirewallPolicy]
906        """
907        if not filters:
908            filters = {}
909        return list(self.network.firewall_policies(**filters))
910
911    @_utils.valid_kwargs('audited', 'description', 'firewall_rules', 'name',
912                         'project_id', 'shared')
913    def update_firewall_policy(self, name_or_id, filters=None, **kwargs):
914        """
915        Updates firewall policy.
916
917        :param name_or_id: firewall policy name or id
918        :param dict filters: optional filters
919        :param kwargs: firewall policy update parameters
920            See create_firewall_policy docstring for valid parameters.
921        :raises: BadRequestException if parameters are malformed
922        :raises: DuplicateResource on multiple matches
923        :raises: ResourceNotFound if resource is not found
924        :return: updated firewall policy
925        :rtype: FirewallPolicy
926        """
927        if not filters:
928            filters = {}
929        firewall_policy = self.network.find_firewall_policy(
930            name_or_id, ignore_missing=False, **filters)
931
932        if 'firewall_rules' in kwargs:
933            kwargs['firewall_rules'] = self._get_firewall_rule_ids(
934                kwargs['firewall_rules'])
935
936        return self.network.update_firewall_policy(firewall_policy, **kwargs)
937
938    def insert_rule_into_policy(self, name_or_id, rule_name_or_id,
939                                insert_after=None, insert_before=None,
940                                filters=None):
941        """
942        Adds firewall rule to the firewall_rules list of a firewall policy.
943        Short-circuits and returns the firewall policy early if the firewall
944        rule id is already present in the firewall_rules list.
945        This method doesn't do re-ordering. If you want to move a firewall rule
946        or down the list, you have to remove and re-add it.
947
948        :param name_or_id: firewall policy name or id
949        :param rule_name_or_id: firewall rule name or id
950        :param insert_after: rule name or id that should precede added rule
951        :param insert_before: rule name or id that should succeed added rule
952        :param dict filters: optional filters
953        :raises: DuplicateResource on multiple matches
954        :raises: ResourceNotFound if firewall policy or any of the firewall
955                 rules (inserted, after, before) is not found.
956        :return: updated firewall policy
957        :rtype: FirewallPolicy
958        """
959        if not filters:
960            filters = {}
961        firewall_policy = self.network.find_firewall_policy(
962            name_or_id, ignore_missing=False, **filters)
963
964        firewall_rule = self.network.find_firewall_rule(
965            rule_name_or_id, ignore_missing=False)
966        # short-circuit if rule already in firewall_rules list
967        # the API can't do any re-ordering of existing rules
968        if firewall_rule['id'] in firewall_policy['firewall_rules']:
969            self.log.debug(
970                'Firewall rule %s already associated with firewall policy %s',
971                rule_name_or_id, name_or_id)
972            return firewall_policy
973
974        pos_params = {}
975        if insert_after is not None:
976            pos_params['insert_after'] = self.network.find_firewall_rule(
977                insert_after, ignore_missing=False)['id']
978
979        if insert_before is not None:
980            pos_params['insert_before'] = self.network.find_firewall_rule(
981                insert_before, ignore_missing=False)['id']
982
983        return self.network.insert_rule_into_policy(firewall_policy['id'],
984                                                    firewall_rule['id'],
985                                                    **pos_params)
986
987    def remove_rule_from_policy(self, name_or_id, rule_name_or_id,
988                                filters=None):
989        """
990        Remove firewall rule from firewall policy's firewall_rules list.
991        Short-circuits and returns firewall policy early if firewall rule
992        is already absent from the firewall_rules list.
993
994        :param name_or_id: firewall policy name or id
995        :param rule_name_or_id: firewall rule name or id
996        :param dict filters: optional filters
997        :raises: DuplicateResource on multiple matches
998        :raises: ResourceNotFound if firewall policy is not found
999        :return: updated firewall policy
1000        :rtype: FirewallPolicy
1001        """
1002        if not filters:
1003            filters = {}
1004        firewall_policy = self.network.find_firewall_policy(
1005            name_or_id, ignore_missing=False, **filters)
1006
1007        firewall_rule = self.network.find_firewall_rule(rule_name_or_id)
1008        if not firewall_rule:
1009            # short-circuit: if firewall rule is not found,
1010            # return current firewall policy
1011            self.log.debug('Firewall rule %s not found for removing',
1012                           rule_name_or_id)
1013            return firewall_policy
1014
1015        if firewall_rule['id'] not in firewall_policy['firewall_rules']:
1016            # short-circuit: if firewall rule id is not associated,
1017            # log it to debug and return current firewall policy
1018            self.log.debug(
1019                'Firewall rule %s not associated with firewall policy %s',
1020                rule_name_or_id, name_or_id)
1021            return firewall_policy
1022
1023        return self.network.remove_rule_from_policy(firewall_policy['id'],
1024                                                    firewall_rule['id'])
1025
1026    @_utils.valid_kwargs(
1027        'admin_state_up', 'description', 'egress_firewall_policy',
1028        'ingress_firewall_policy', 'name', 'ports', 'project_id', 'shared')
1029    def create_firewall_group(self, **kwargs):
1030        """
1031        Creates firewall group. The keys egress_firewall_policy and
1032        ingress_firewall_policy are looked up and mapped as
1033        egress_firewall_policy_id and ingress_firewall_policy_id respectively.
1034        Port name or ids list is transformed to port ids list before the POST
1035        request.
1036
1037        :param bool admin_state_up: State of firewall group.
1038                                    Will block all traffic if set to False.
1039                                    Defaults to True.
1040        :param description: Human-readable description.
1041        :param egress_firewall_policy: Name or id of egress firewall policy.
1042        :param ingress_firewall_policy: Name or id of ingress firewall policy.
1043        :param name: Human-readable name.
1044        :param list[str] ports: List of associated ports (name or id)
1045        :param project_id: Project id.
1046        :param shared: Visibility to other projects.
1047                       Defaults to False.
1048        :raises: BadRequestException if parameters are malformed
1049        :raises: DuplicateResource on multiple matches
1050        :raises: ResourceNotFound if (ingress-, egress-) firewall policy or
1051                 a port is not found.
1052        :return: created firewall group
1053        :rtype: FirewallGroup
1054        """
1055        self._lookup_ingress_egress_firewall_policy_ids(kwargs)
1056        if 'ports' in kwargs:
1057            kwargs['ports'] = self._get_port_ids(kwargs['ports'])
1058        return self.network.create_firewall_group(**kwargs)
1059
1060    def delete_firewall_group(self, name_or_id, filters=None):
1061        """
1062        Deletes firewall group.
1063        Prints debug message in case to-be-deleted resource was not found.
1064
1065        :param name_or_id: firewall group name or id
1066        :param dict filters: optional filters
1067        :raises: DuplicateResource on multiple matches
1068        :return: True if resource is successfully deleted, False otherwise.
1069        :rtype: bool
1070        """
1071        if not filters:
1072            filters = {}
1073        try:
1074            firewall_group = self.network.find_firewall_group(
1075                name_or_id, ignore_missing=False, **filters)
1076            self.network.delete_firewall_group(firewall_group,
1077                                               ignore_missing=False)
1078        except exceptions.ResourceNotFound:
1079            self.log.debug('Firewall group %s not found for deleting',
1080                           name_or_id)
1081            return False
1082        return True
1083
1084    def get_firewall_group(self, name_or_id, filters=None):
1085        """
1086        Retrieves firewall group.
1087
1088        :param name_or_id: firewall group name or id
1089        :param dict filters: optional filters
1090        :raises: DuplicateResource on multiple matches
1091        :return: firewall group or None if not found
1092        :rtype: FirewallGroup
1093        """
1094        if not filters:
1095            filters = {}
1096        return self.network.find_firewall_group(name_or_id, **filters)
1097
1098    def list_firewall_groups(self, filters=None):
1099        """
1100        Lists firewall groups.
1101
1102        :param dict filters: optional filters
1103        :return: list of firewall groups
1104        :rtype: list[FirewallGroup]
1105        """
1106        if not filters:
1107            filters = {}
1108        return list(self.network.firewall_groups(**filters))
1109
1110    @_utils.valid_kwargs(
1111        'admin_state_up', 'description', 'egress_firewall_policy',
1112        'ingress_firewall_policy', 'name', 'ports', 'project_id', 'shared')
1113    def update_firewall_group(self, name_or_id, filters=None, **kwargs):
1114        """
1115        Updates firewall group.
1116        To unset egress- or ingress firewall policy, set egress_firewall_policy
1117        or ingress_firewall_policy to None. You can also set
1118        egress_firewall_policy_id and ingress_firewall_policy_id directly,
1119        which will skip the policy lookups.
1120
1121        :param name_or_id: firewall group name or id
1122        :param dict filters: optional filters
1123        :param kwargs: firewall group update parameters
1124            See create_firewall_group docstring for valid parameters.
1125        :raises: BadRequestException if parameters are malformed
1126        :raises: DuplicateResource on multiple matches
1127        :raises: ResourceNotFound if firewall group, a firewall policy
1128                 (egress, ingress) or port is not found
1129        :return: updated firewall group
1130        :rtype: FirewallGroup
1131        """
1132        if not filters:
1133            filters = {}
1134        firewall_group = self.network.find_firewall_group(
1135            name_or_id, ignore_missing=False, **filters)
1136        self._lookup_ingress_egress_firewall_policy_ids(kwargs)
1137
1138        if 'ports' in kwargs:
1139            kwargs['ports'] = self._get_port_ids(kwargs['ports'])
1140        return self.network.update_firewall_group(firewall_group, **kwargs)
1141
1142    def _lookup_ingress_egress_firewall_policy_ids(self, firewall_group):
1143        """
1144        Transforms firewall_group dict IN-PLACE. Takes the value of the keys
1145        egress_firewall_policy and ingress_firewall_policy, looks up the
1146        policy ids and maps them to egress_firewall_policy_id and
1147        ingress_firewall_policy_id. Old keys which were used for the lookup
1148        are deleted.
1149
1150        :param dict firewall_group: firewall group dict
1151        :raises: DuplicateResource on multiple matches
1152        :raises: ResourceNotFound if a firewall policy is not found
1153        """
1154        for key in ('egress_firewall_policy', 'ingress_firewall_policy'):
1155            if key not in firewall_group:
1156                continue
1157            if firewall_group[key] is None:
1158                val = None
1159            else:
1160                val = self.network.find_firewall_policy(
1161                    firewall_group[key], ignore_missing=False)['id']
1162            firewall_group[key + '_id'] = val
1163            del firewall_group[key]
1164
1165    @_utils.valid_kwargs("name", "description", "shared", "default",
1166                         "project_id")
1167    def create_qos_policy(self, **kwargs):
1168        """Create a QoS policy.
1169
1170        :param string name: Name of the QoS policy being created.
1171        :param string description: Description of created QoS policy.
1172        :param bool shared: Set the QoS policy as shared.
1173        :param bool default: Set the QoS policy as default for project.
1174        :param string project_id: Specify the project ID this QoS policy
1175            will be created on (admin-only).
1176
1177        :returns: The QoS policy object.
1178        :raises: OpenStackCloudException on operation error.
1179        """
1180        if not self._has_neutron_extension('qos'):
1181            raise exc.OpenStackCloudUnavailableExtension(
1182                'QoS extension is not available on target cloud')
1183
1184        default = kwargs.pop("default", None)
1185        if default is not None:
1186            if self._has_neutron_extension('qos-default'):
1187                kwargs['is_default'] = default
1188            else:
1189                self.log.debug("'qos-default' extension is not available on "
1190                               "target cloud")
1191
1192        return self.network.create_qos_policy(**kwargs)
1193
1194    @_utils.valid_kwargs("name", "description", "shared", "default",
1195                         "project_id")
1196    def update_qos_policy(self, name_or_id, **kwargs):
1197        """Update an existing QoS policy.
1198
1199        :param string name_or_id:
1200           Name or ID of the QoS policy to update.
1201        :param string policy_name:
1202           The new name of the QoS policy.
1203        :param string description:
1204            The new description of the QoS policy.
1205        :param bool shared:
1206            If True, the QoS policy will be set as shared.
1207        :param bool default:
1208            If True, the QoS policy will be set as default for project.
1209
1210        :returns: The updated QoS policy object.
1211        :raises: OpenStackCloudException on operation error.
1212        """
1213        if not self._has_neutron_extension('qos'):
1214            raise exc.OpenStackCloudUnavailableExtension(
1215                'QoS extension is not available on target cloud')
1216
1217        default = kwargs.pop("default", None)
1218        if default is not None:
1219            if self._has_neutron_extension('qos-default'):
1220                kwargs['is_default'] = default
1221            else:
1222                self.log.debug("'qos-default' extension is not available on "
1223                               "target cloud")
1224
1225        if not kwargs:
1226            self.log.debug("No QoS policy data to update")
1227            return
1228
1229        curr_policy = self.network.find_qos_policy(name_or_id)
1230        if not curr_policy:
1231            raise exc.OpenStackCloudException(
1232                "QoS policy %s not found." % name_or_id)
1233        return self.network.update_qos_policy(curr_policy, **kwargs)
1234
1235    def delete_qos_policy(self, name_or_id):
1236        """Delete a QoS policy.
1237
1238        :param name_or_id: Name or ID of the policy being deleted.
1239
1240        :returns: True if delete succeeded, False otherwise.
1241
1242        :raises: OpenStackCloudException on operation error.
1243        """
1244        if not self._has_neutron_extension('qos'):
1245            raise exc.OpenStackCloudUnavailableExtension(
1246                'QoS extension is not available on target cloud')
1247        policy = self.network.find_qos_policy(name_or_id)
1248        if not policy:
1249            self.log.debug("QoS policy %s not found for deleting", name_or_id)
1250            return False
1251        self.network.delete_qos_policy(policy)
1252
1253        return True
1254
1255    def search_qos_bandwidth_limit_rules(
1256        self, policy_name_or_id, rule_id=None, filters=None
1257    ):
1258        """Search QoS bandwidth limit rules
1259
1260        :param string policy_name_or_id: Name or ID of the QoS policy to which
1261            rules should be associated.
1262        :param string rule_id: ID of searched rule.
1263        :param filters: a dict containing additional filters to use. e.g.
1264                        {'max_kbps': 1000}
1265
1266        :returns: a list of ``munch.Munch`` containing the bandwidth limit
1267            rule descriptions.
1268
1269        :raises: ``OpenStackCloudException`` if something goes wrong during the
1270            OpenStack API call.
1271        """
1272        rules = self.list_qos_bandwidth_limit_rules(policy_name_or_id, filters)
1273        return _utils._filter_list(rules, rule_id, filters)
1274
1275    def list_qos_bandwidth_limit_rules(self, policy_name_or_id, filters=None):
1276        """List all available QoS bandwidth limit rules.
1277
1278        :param string policy_name_or_id: Name or ID of the QoS policy from
1279            from rules should be listed.
1280        :param filters: (optional) dict of filter conditions to push down
1281        :returns: A list of ``munch.Munch`` containing rule info.
1282
1283        :raises: ``OpenStackCloudResourceNotFound`` if QoS policy will not be
1284            found.
1285        """
1286        if not self._has_neutron_extension('qos'):
1287            raise exc.OpenStackCloudUnavailableExtension(
1288                'QoS extension is not available on target cloud')
1289
1290        policy = self.network.find_qos_policy(policy_name_or_id)
1291        if not policy:
1292            raise exc.OpenStackCloudResourceNotFound(
1293                "QoS policy {name_or_id} not Found.".format(
1294                    name_or_id=policy_name_or_id))
1295
1296        # Translate None from search interface to empty {} for kwargs below
1297        if not filters:
1298            filters = {}
1299
1300        return list(self.network.qos_bandwidth_limit_rules(
1301            qos_policy=policy, **filters))
1302
1303    def get_qos_bandwidth_limit_rule(self, policy_name_or_id, rule_id):
1304        """Get a QoS bandwidth limit rule by name or ID.
1305
1306        :param string policy_name_or_id: Name or ID of the QoS policy to which
1307            rule should be associated.
1308        :param rule_id: ID of the rule.
1309
1310        :returns: A bandwidth limit rule ``munch.Munch`` or None if
1311            no matching rule is found.
1312
1313        """
1314        if not self._has_neutron_extension('qos'):
1315            raise exc.OpenStackCloudUnavailableExtension(
1316                'QoS extension is not available on target cloud')
1317
1318        policy = self.network.find_qos_policy(policy_name_or_id)
1319        if not policy:
1320            raise exc.OpenStackCloudResourceNotFound(
1321                "QoS policy {name_or_id} not Found.".format(
1322                    name_or_id=policy_name_or_id))
1323
1324        return self.network.get_qos_bandwidth_limit_rule(
1325            rule_id, policy)
1326
1327    @_utils.valid_kwargs("max_burst_kbps", "direction")
1328    def create_qos_bandwidth_limit_rule(self, policy_name_or_id, max_kbps,
1329                                        **kwargs):
1330        """Create a QoS bandwidth limit rule.
1331
1332        :param string policy_name_or_id: Name or ID of the QoS policy to which
1333            rule should be associated.
1334        :param int max_kbps: Maximum bandwidth limit value
1335            (in kilobits per second).
1336        :param int max_burst_kbps: Maximum burst value (in kilobits).
1337        :param string direction: Ingress or egress.
1338            The direction in which the traffic will be limited.
1339
1340        :returns: The QoS bandwidth limit rule.
1341        :raises: OpenStackCloudException on operation error.
1342        """
1343        if not self._has_neutron_extension('qos'):
1344            raise exc.OpenStackCloudUnavailableExtension(
1345                'QoS extension is not available on target cloud')
1346
1347        policy = self.network.find_qos_policy(policy_name_or_id)
1348        if not policy:
1349            raise exc.OpenStackCloudResourceNotFound(
1350                "QoS policy {name_or_id} not Found.".format(
1351                    name_or_id=policy_name_or_id))
1352
1353        if kwargs.get("direction") is not None:
1354            if not self._has_neutron_extension('qos-bw-limit-direction'):
1355                kwargs.pop("direction")
1356                self.log.debug(
1357                    "'qos-bw-limit-direction' extension is not available on "
1358                    "target cloud")
1359
1360        kwargs['max_kbps'] = max_kbps
1361        return self.network.create_qos_bandwidth_limit_rule(policy, **kwargs)
1362
1363    @_utils.valid_kwargs("max_kbps", "max_burst_kbps", "direction")
1364    def update_qos_bandwidth_limit_rule(self, policy_name_or_id, rule_id,
1365                                        **kwargs):
1366        """Update a QoS bandwidth limit rule.
1367
1368        :param string policy_name_or_id: Name or ID of the QoS policy to which
1369            rule is associated.
1370        :param string rule_id: ID of rule to update.
1371        :param int max_kbps: Maximum bandwidth limit value
1372            (in kilobits per second).
1373        :param int max_burst_kbps: Maximum burst value (in kilobits).
1374        :param string direction: Ingress or egress.
1375            The direction in which the traffic will be limited.
1376
1377        :returns: The updated QoS bandwidth limit rule.
1378        :raises: OpenStackCloudException on operation error.
1379        """
1380        if not self._has_neutron_extension('qos'):
1381            raise exc.OpenStackCloudUnavailableExtension(
1382                'QoS extension is not available on target cloud')
1383
1384        policy = self.network.find_qos_policy(
1385            policy_name_or_id,
1386            ignore_missing=True)
1387        if not policy:
1388            raise exc.OpenStackCloudResourceNotFound(
1389                "QoS policy {name_or_id} not Found.".format(
1390                    name_or_id=policy_name_or_id))
1391
1392        if kwargs.get("direction") is not None:
1393            if not self._has_neutron_extension('qos-bw-limit-direction'):
1394                kwargs.pop("direction")
1395                self.log.debug(
1396                    "'qos-bw-limit-direction' extension is not available on "
1397                    "target cloud")
1398
1399        if not kwargs:
1400            self.log.debug("No QoS bandwidth limit rule data to update")
1401            return
1402
1403        curr_rule = self.network.get_qos_bandwidth_limit_rule(
1404            qos_rule=rule_id, qos_policy=policy)
1405        if not curr_rule:
1406            raise exc.OpenStackCloudException(
1407                "QoS bandwidth_limit_rule {rule_id} not found in policy "
1408                "{policy_id}".format(rule_id=rule_id,
1409                                     policy_id=policy['id']))
1410
1411        return self.network.update_qos_bandwidth_limit_rule(
1412            qos_rule=curr_rule, qos_policy=policy, **kwargs)
1413
1414    def delete_qos_bandwidth_limit_rule(self, policy_name_or_id, rule_id):
1415        """Delete a QoS bandwidth limit rule.
1416
1417        :param string policy_name_or_id: Name or ID of the QoS policy to which
1418            rule is associated.
1419        :param string rule_id: ID of rule to update.
1420
1421        :raises: OpenStackCloudException on operation error.
1422        """
1423        if not self._has_neutron_extension('qos'):
1424            raise exc.OpenStackCloudUnavailableExtension(
1425                'QoS extension is not available on target cloud')
1426
1427        policy = self.network.find_qos_policy(policy_name_or_id)
1428        if not policy:
1429            raise exc.OpenStackCloudResourceNotFound(
1430                "QoS policy {name_or_id} not Found.".format(
1431                    name_or_id=policy_name_or_id))
1432
1433        try:
1434            self.network.delete_qos_bandwidth_limit_rule(
1435                rule_id, policy, ignore_missing=False)
1436        except exceptions.ResourceNotFound:
1437            self.log.debug(
1438                "QoS bandwidth limit rule {rule_id} not found in policy "
1439                "{policy_id}. Ignoring.".format(rule_id=rule_id,
1440                                                policy_id=policy['id']))
1441            return False
1442
1443        return True
1444
1445    def search_qos_dscp_marking_rules(self, policy_name_or_id, rule_id=None,
1446                                      filters=None):
1447        """Search QoS DSCP marking rules
1448
1449        :param string policy_name_or_id: Name or ID of the QoS policy to which
1450            rules should be associated.
1451        :param string rule_id: ID of searched rule.
1452        :param filters: a dict containing additional filters to use. e.g.
1453                        {'dscp_mark': 32}
1454
1455        :returns: a list of ``munch.Munch`` containing the dscp marking
1456            rule descriptions.
1457
1458        :raises: ``OpenStackCloudException`` if something goes wrong during the
1459            OpenStack API call.
1460        """
1461        rules = self.list_qos_dscp_marking_rules(policy_name_or_id, filters)
1462        return _utils._filter_list(rules, rule_id, filters)
1463
1464    def list_qos_dscp_marking_rules(self, policy_name_or_id, filters=None):
1465        """List all available QoS DSCP marking rules.
1466
1467        :param string policy_name_or_id: Name or ID of the QoS policy from
1468            from rules should be listed.
1469        :param filters: (optional) dict of filter conditions to push down
1470        :returns: A list of ``munch.Munch`` containing rule info.
1471
1472        :raises: ``OpenStackCloudResourceNotFound`` if QoS policy will not be
1473            found.
1474        """
1475        if not self._has_neutron_extension('qos'):
1476            raise exc.OpenStackCloudUnavailableExtension(
1477                'QoS extension is not available on target cloud')
1478
1479        policy = self.network.find_qos_policy(
1480            policy_name_or_id, ignore_missing=True)
1481        if not policy:
1482            raise exc.OpenStackCloudResourceNotFound(
1483                "QoS policy {name_or_id} not Found.".format(
1484                    name_or_id=policy_name_or_id))
1485
1486        # Translate None from search interface to empty {} for kwargs below
1487        if not filters:
1488            filters = {}
1489
1490        return list(self.network.qos_dscp_marking_rules(policy, **filters))
1491
1492    def get_qos_dscp_marking_rule(self, policy_name_or_id, rule_id):
1493        """Get a QoS DSCP marking rule by name or ID.
1494
1495        :param string policy_name_or_id: Name or ID of the QoS policy to which
1496            rule should be associated.
1497        :param rule_id: ID of the rule.
1498
1499        :returns: A bandwidth limit rule ``munch.Munch`` or None if
1500            no matching rule is found.
1501
1502        """
1503        if not self._has_neutron_extension('qos'):
1504            raise exc.OpenStackCloudUnavailableExtension(
1505                'QoS extension is not available on target cloud')
1506
1507        policy = self.network.find_qos_policy(policy_name_or_id)
1508        if not policy:
1509            raise exc.OpenStackCloudResourceNotFound(
1510                "QoS policy {name_or_id} not Found.".format(
1511                    name_or_id=policy_name_or_id))
1512
1513        return self.network.get_qos_dscp_marking_rule(rule_id, policy)
1514
1515    def create_qos_dscp_marking_rule(self, policy_name_or_id, dscp_mark):
1516        """Create a QoS DSCP marking rule.
1517
1518        :param string policy_name_or_id: Name or ID of the QoS policy to which
1519            rule should be associated.
1520        :param int dscp_mark: DSCP mark value
1521
1522        :returns: The QoS DSCP marking rule.
1523        :raises: OpenStackCloudException on operation error.
1524        """
1525        if not self._has_neutron_extension('qos'):
1526            raise exc.OpenStackCloudUnavailableExtension(
1527                'QoS extension is not available on target cloud')
1528
1529        policy = self.network.find_qos_policy(policy_name_or_id)
1530        if not policy:
1531            raise exc.OpenStackCloudResourceNotFound(
1532                "QoS policy {name_or_id} not Found.".format(
1533                    name_or_id=policy_name_or_id))
1534
1535        return self.network.create_qos_dscp_marking_rule(
1536            policy, dscp_mark=dscp_mark)
1537
1538    @_utils.valid_kwargs("dscp_mark")
1539    def update_qos_dscp_marking_rule(self, policy_name_or_id, rule_id,
1540                                     **kwargs):
1541        """Update a QoS DSCP marking rule.
1542
1543        :param string policy_name_or_id: Name or ID of the QoS policy to which
1544            rule is associated.
1545        :param string rule_id: ID of rule to update.
1546        :param int dscp_mark: DSCP mark value
1547
1548        :returns: The updated QoS bandwidth limit rule.
1549        :raises: OpenStackCloudException on operation error.
1550        """
1551        if not self._has_neutron_extension('qos'):
1552            raise exc.OpenStackCloudUnavailableExtension(
1553                'QoS extension is not available on target cloud')
1554
1555        policy = self.network.find_qos_policy(policy_name_or_id)
1556        if not policy:
1557            raise exc.OpenStackCloudResourceNotFound(
1558                "QoS policy {name_or_id} not Found.".format(
1559                    name_or_id=policy_name_or_id))
1560
1561        if not kwargs:
1562            self.log.debug("No QoS DSCP marking rule data to update")
1563            return
1564
1565        curr_rule = self.network.get_qos_dscp_marking_rule(
1566            rule_id, policy)
1567        if not curr_rule:
1568            raise exc.OpenStackCloudException(
1569                "QoS dscp_marking_rule {rule_id} not found in policy "
1570                "{policy_id}".format(rule_id=rule_id,
1571                                     policy_id=policy['id']))
1572
1573        return self.network.update_qos_dscp_marking_rule(
1574            curr_rule, policy, **kwargs)
1575
1576    def delete_qos_dscp_marking_rule(self, policy_name_or_id, rule_id):
1577        """Delete a QoS DSCP marking rule.
1578
1579        :param string policy_name_or_id: Name or ID of the QoS policy to which
1580            rule is associated.
1581        :param string rule_id: ID of rule to update.
1582
1583        :raises: OpenStackCloudException on operation error.
1584        """
1585        if not self._has_neutron_extension('qos'):
1586            raise exc.OpenStackCloudUnavailableExtension(
1587                'QoS extension is not available on target cloud')
1588
1589        policy = self.network.find_qos_policy(policy_name_or_id)
1590        if not policy:
1591            raise exc.OpenStackCloudResourceNotFound(
1592                "QoS policy {name_or_id} not Found.".format(
1593                    name_or_id=policy_name_or_id))
1594
1595        try:
1596            self.network.delete_qos_dscp_marking_rule(
1597                rule_id, policy, ignore_missing=False)
1598        except exceptions.ResourceNotFound:
1599            self.log.debug(
1600                "QoS DSCP marking rule {rule_id} not found in policy "
1601                "{policy_id}. Ignoring.".format(rule_id=rule_id,
1602                                                policy_id=policy['id']))
1603            return False
1604
1605        return True
1606
1607    def search_qos_minimum_bandwidth_rules(self, policy_name_or_id,
1608                                           rule_id=None, filters=None):
1609        """Search QoS minimum bandwidth rules
1610
1611        :param string policy_name_or_id: Name or ID of the QoS policy to which
1612            rules should be associated.
1613        :param string rule_id: ID of searched rule.
1614        :param filters: a dict containing additional filters to use. e.g.
1615                        {'min_kbps': 1000}
1616
1617        :returns: a list of ``munch.Munch`` containing the bandwidth limit
1618            rule descriptions.
1619
1620        :raises: ``OpenStackCloudException`` if something goes wrong during the
1621            OpenStack API call.
1622        """
1623        rules = self.list_qos_minimum_bandwidth_rules(
1624            policy_name_or_id, filters)
1625        return _utils._filter_list(rules, rule_id, filters)
1626
1627    def list_qos_minimum_bandwidth_rules(self, policy_name_or_id,
1628                                         filters=None):
1629        """List all available QoS minimum bandwidth rules.
1630
1631        :param string policy_name_or_id: Name or ID of the QoS policy from
1632            from rules should be listed.
1633        :param filters: (optional) dict of filter conditions to push down
1634        :returns: A list of ``munch.Munch`` containing rule info.
1635
1636        :raises: ``OpenStackCloudResourceNotFound`` if QoS policy will not be
1637            found.
1638        """
1639        if not self._has_neutron_extension('qos'):
1640            raise exc.OpenStackCloudUnavailableExtension(
1641                'QoS extension is not available on target cloud')
1642
1643        policy = self.network.find_qos_policy(policy_name_or_id)
1644        if not policy:
1645            raise exc.OpenStackCloudResourceNotFound(
1646                "QoS policy {name_or_id} not Found.".format(
1647                    name_or_id=policy_name_or_id))
1648
1649        # Translate None from search interface to empty {} for kwargs below
1650        if not filters:
1651            filters = {}
1652
1653        return list(
1654            self.network.qos_minimum_bandwidth_rules(
1655                policy, **filters))
1656
1657    def get_qos_minimum_bandwidth_rule(self, policy_name_or_id, rule_id):
1658        """Get a QoS minimum bandwidth rule by name or ID.
1659
1660        :param string policy_name_or_id: Name or ID of the QoS policy to which
1661            rule should be associated.
1662        :param rule_id: ID of the rule.
1663
1664        :returns: A bandwidth limit rule ``munch.Munch`` or None if
1665            no matching rule is found.
1666
1667        """
1668        if not self._has_neutron_extension('qos'):
1669            raise exc.OpenStackCloudUnavailableExtension(
1670                'QoS extension is not available on target cloud')
1671
1672        policy = self.network.find_qos_policy(policy_name_or_id)
1673        if not policy:
1674            raise exc.OpenStackCloudResourceNotFound(
1675                "QoS policy {name_or_id} not Found.".format(
1676                    name_or_id=policy_name_or_id))
1677
1678        return self.network.get_qos_minimum_bandwidth_rule(rule_id, policy)
1679
1680    @_utils.valid_kwargs("direction")
1681    def create_qos_minimum_bandwidth_rule(
1682        self, policy_name_or_id, min_kbps, **kwargs
1683    ):
1684        """Create a QoS minimum bandwidth limit rule.
1685
1686        :param string policy_name_or_id: Name or ID of the QoS policy to which
1687            rule should be associated.
1688        :param int min_kbps: Minimum bandwidth value (in kilobits per second).
1689        :param string direction: Ingress or egress.
1690            The direction in which the traffic will be available.
1691
1692        :returns: The QoS minimum bandwidth rule.
1693        :raises: OpenStackCloudException on operation error.
1694        """
1695        if not self._has_neutron_extension('qos'):
1696            raise exc.OpenStackCloudUnavailableExtension(
1697                'QoS extension is not available on target cloud')
1698
1699        policy = self.network.find_qos_policy(policy_name_or_id)
1700        if not policy:
1701            raise exc.OpenStackCloudResourceNotFound(
1702                "QoS policy {name_or_id} not Found.".format(
1703                    name_or_id=policy_name_or_id))
1704
1705        kwargs['min_kbps'] = min_kbps
1706        return self.network.create_qos_minimum_bandwidth_rule(policy, **kwargs)
1707
1708    @_utils.valid_kwargs("min_kbps", "direction")
1709    def update_qos_minimum_bandwidth_rule(
1710        self, policy_name_or_id, rule_id, **kwargs
1711    ):
1712        """Update a QoS minimum bandwidth rule.
1713
1714        :param string policy_name_or_id: Name or ID of the QoS policy to which
1715            rule is associated.
1716        :param string rule_id: ID of rule to update.
1717        :param int min_kbps: Minimum bandwidth value (in kilobits per second).
1718        :param string direction: Ingress or egress.
1719            The direction in which the traffic will be available.
1720
1721        :returns: The updated QoS minimum bandwidth rule.
1722        :raises: OpenStackCloudException on operation error.
1723        """
1724        if not self._has_neutron_extension('qos'):
1725            raise exc.OpenStackCloudUnavailableExtension(
1726                'QoS extension is not available on target cloud')
1727
1728        policy = self.network.find_qos_policy(policy_name_or_id)
1729        if not policy:
1730            raise exc.OpenStackCloudResourceNotFound(
1731                "QoS policy {name_or_id} not Found.".format(
1732                    name_or_id=policy_name_or_id))
1733
1734        if not kwargs:
1735            self.log.debug("No QoS minimum bandwidth rule data to update")
1736            return
1737
1738        curr_rule = self.network.get_qos_minimum_bandwidth_rule(
1739            rule_id, policy)
1740        if not curr_rule:
1741            raise exc.OpenStackCloudException(
1742                "QoS minimum_bandwidth_rule {rule_id} not found in policy "
1743                "{policy_id}".format(rule_id=rule_id,
1744                                     policy_id=policy['id']))
1745
1746        return self.network.update_qos_minimum_bandwidth_rule(
1747            curr_rule, policy, **kwargs)
1748
1749    def delete_qos_minimum_bandwidth_rule(self, policy_name_or_id, rule_id):
1750        """Delete a QoS minimum bandwidth rule.
1751
1752        :param string policy_name_or_id: Name or ID of the QoS policy to which
1753            rule is associated.
1754        :param string rule_id: ID of rule to delete.
1755
1756        :raises: OpenStackCloudException on operation error.
1757        """
1758        if not self._has_neutron_extension('qos'):
1759            raise exc.OpenStackCloudUnavailableExtension(
1760                'QoS extension is not available on target cloud')
1761
1762        policy = self.network.find_qos_policy(policy_name_or_id)
1763        if not policy:
1764            raise exc.OpenStackCloudResourceNotFound(
1765                "QoS policy {name_or_id} not Found.".format(
1766                    name_or_id=policy_name_or_id))
1767
1768        try:
1769            self.network.delete_qos_minimum_bandwidth_rule(
1770                rule_id, policy, ignore_missing=False)
1771        except exceptions.ResourceNotFound:
1772            self.log.debug(
1773                "QoS minimum bandwidth rule {rule_id} not found in policy "
1774                "{policy_id}. Ignoring.".format(rule_id=rule_id,
1775                                                policy_id=policy['id']))
1776            return False
1777
1778        return True
1779
1780    def add_router_interface(self, router, subnet_id=None, port_id=None):
1781        """Attach a subnet to an internal router interface.
1782
1783        Either a subnet ID or port ID must be specified for the internal
1784        interface. Supplying both will result in an error.
1785
1786        :param dict router: The dict object of the router being changed
1787        :param string subnet_id: The ID of the subnet to use for the interface
1788        :param string port_id: The ID of the port to use for the interface
1789
1790        :returns: A ``munch.Munch`` with the router ID (ID),
1791                  subnet ID (subnet_id), port ID (port_id) and tenant ID
1792                  (tenant_id).
1793
1794        :raises: OpenStackCloudException on operation error.
1795        """
1796        json_body = {}
1797        if subnet_id:
1798            json_body['subnet_id'] = subnet_id
1799        if port_id:
1800            json_body['port_id'] = port_id
1801
1802        return proxy._json_response(
1803            self.network.put(
1804                "/routers/{router_id}/add_router_interface".format(
1805                    router_id=router['id']),
1806                json=json_body),
1807            error_message="Error attaching interface to router {0}".format(
1808                router['id']))
1809
1810    def remove_router_interface(self, router, subnet_id=None, port_id=None):
1811        """Detach a subnet from an internal router interface.
1812
1813        At least one of subnet_id or port_id must be supplied.
1814
1815        If you specify both subnet and port ID, the subnet ID must
1816        correspond to the subnet ID of the first IP address on the port
1817        specified by the port ID. Otherwise an error occurs.
1818
1819        :param dict router: The dict object of the router being changed
1820        :param string subnet_id: The ID of the subnet to use for the interface
1821        :param string port_id: The ID of the port to use for the interface
1822
1823        :returns: None on success
1824
1825        :raises: OpenStackCloudException on operation error.
1826        """
1827        json_body = {}
1828        if subnet_id:
1829            json_body['subnet_id'] = subnet_id
1830        if port_id:
1831            json_body['port_id'] = port_id
1832
1833        if not json_body:
1834            raise ValueError(
1835                "At least one of subnet_id or port_id must be supplied.")
1836
1837        exceptions.raise_from_response(
1838            self.network.put(
1839                "/routers/{router_id}/remove_router_interface".format(
1840                    router_id=router['id']),
1841                json=json_body),
1842            error_message="Error detaching interface from router {0}".format(
1843                router['id']))
1844
1845    def list_router_interfaces(self, router, interface_type=None):
1846        """List all interfaces for a router.
1847
1848        :param dict router: A router dict object.
1849        :param string interface_type: One of None, "internal", or "external".
1850            Controls whether all, internal interfaces or external interfaces
1851            are returned.
1852
1853        :returns: A list of port ``munch.Munch`` objects.
1854        """
1855        # Find only router interface and gateway ports, ignore L3 HA ports etc.
1856        router_interfaces = self.search_ports(filters={
1857            'device_id': router['id'],
1858            'device_owner': 'network:router_interface'}
1859        ) + self.search_ports(filters={
1860            'device_id': router['id'],
1861            'device_owner': 'network:router_interface_distributed'}
1862        ) + self.search_ports(filters={
1863            'device_id': router['id'],
1864            'device_owner': 'network:ha_router_replicated_interface'})
1865        router_gateways = self.search_ports(filters={
1866            'device_id': router['id'],
1867            'device_owner': 'network:router_gateway'})
1868        ports = router_interfaces + router_gateways
1869
1870        if interface_type:
1871            if interface_type == 'internal':
1872                return router_interfaces
1873            if interface_type == 'external':
1874                return router_gateways
1875        return ports
1876
1877    def create_router(self, name=None, admin_state_up=True,
1878                      ext_gateway_net_id=None, enable_snat=None,
1879                      ext_fixed_ips=None, project_id=None,
1880                      availability_zone_hints=None):
1881        """Create a logical router.
1882
1883        :param string name: The router name.
1884        :param bool admin_state_up: The administrative state of the router.
1885        :param string ext_gateway_net_id: Network ID for the external gateway.
1886        :param bool enable_snat: Enable Source NAT (SNAT) attribute.
1887        :param ext_fixed_ips:
1888            List of dictionaries of desired IP and/or subnet on the
1889            external network. Example::
1890
1891              [
1892                {
1893                  "subnet_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b",
1894                  "ip_address": "192.168.10.2"
1895                }
1896              ]
1897        :param string project_id: Project ID for the router.
1898        :param types.ListType availability_zone_hints:
1899            A list of availability zone hints.
1900
1901        :returns: The router object.
1902        :raises: OpenStackCloudException on operation error.
1903        """
1904        router = {
1905            'admin_state_up': admin_state_up
1906        }
1907        if project_id is not None:
1908            router['tenant_id'] = project_id
1909        if name:
1910            router['name'] = name
1911        ext_gw_info = self._build_external_gateway_info(
1912            ext_gateway_net_id, enable_snat, ext_fixed_ips
1913        )
1914        if ext_gw_info:
1915            router['external_gateway_info'] = ext_gw_info
1916        if availability_zone_hints is not None:
1917            if not isinstance(availability_zone_hints, list):
1918                raise exc.OpenStackCloudException(
1919                    "Parameter 'availability_zone_hints' must be a list")
1920            if not self._has_neutron_extension('router_availability_zone'):
1921                raise exc.OpenStackCloudUnavailableExtension(
1922                    'router_availability_zone extension is not available on '
1923                    'target cloud')
1924            router['availability_zone_hints'] = availability_zone_hints
1925
1926        data = proxy._json_response(
1927            self.network.post("/routers", json={"router": router}),
1928            error_message="Error creating router {0}".format(name))
1929        return self._get_and_munchify('router', data)
1930
1931    def update_router(self, name_or_id, name=None, admin_state_up=None,
1932                      ext_gateway_net_id=None, enable_snat=None,
1933                      ext_fixed_ips=None, routes=None):
1934        """Update an existing logical router.
1935
1936        :param string name_or_id: The name or UUID of the router to update.
1937        :param string name: The new router name.
1938        :param bool admin_state_up: The administrative state of the router.
1939        :param string ext_gateway_net_id:
1940            The network ID for the external gateway.
1941        :param bool enable_snat: Enable Source NAT (SNAT) attribute.
1942        :param ext_fixed_ips:
1943            List of dictionaries of desired IP and/or subnet on the
1944            external network. Example::
1945
1946              [
1947                {
1948                  "subnet_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b",
1949                  "ip_address": "192.168.10.2"
1950                }
1951              ]
1952        :param list routes:
1953            A list of dictionaries with destination and nexthop parameters. To
1954            clear all routes pass an empty list ([]).
1955
1956            Example::
1957
1958              [
1959                {
1960                  "destination": "179.24.1.0/24",
1961                  "nexthop": "172.24.3.99"
1962                }
1963              ]
1964        :returns: The router object.
1965        :raises: OpenStackCloudException on operation error.
1966        """
1967        router = {}
1968        if name:
1969            router['name'] = name
1970        if admin_state_up is not None:
1971            router['admin_state_up'] = admin_state_up
1972        ext_gw_info = self._build_external_gateway_info(
1973            ext_gateway_net_id, enable_snat, ext_fixed_ips
1974        )
1975        if ext_gw_info:
1976            router['external_gateway_info'] = ext_gw_info
1977
1978        if routes is not None:
1979            if self._has_neutron_extension('extraroute'):
1980                router['routes'] = routes
1981            else:
1982                self.log.warning(
1983                    'extra routes extension is not available on target cloud')
1984
1985        if not router:
1986            self.log.debug("No router data to update")
1987            return
1988
1989        curr_router = self.get_router(name_or_id)
1990        if not curr_router:
1991            raise exc.OpenStackCloudException(
1992                "Router %s not found." % name_or_id)
1993
1994        resp = self.network.put(
1995            "/routers/{router_id}".format(router_id=curr_router['id']),
1996            json={"router": router})
1997        data = proxy._json_response(
1998            resp,
1999            error_message="Error updating router {0}".format(name_or_id))
2000        return self._get_and_munchify('router', data)
2001
2002    def delete_router(self, name_or_id):
2003        """Delete a logical router.
2004
2005        If a name, instead of a unique UUID, is supplied, it is possible
2006        that we could find more than one matching router since names are
2007        not required to be unique. An error will be raised in this case.
2008
2009        :param name_or_id: Name or ID of the router being deleted.
2010
2011        :returns: True if delete succeeded, False otherwise.
2012
2013        :raises: OpenStackCloudException on operation error.
2014        """
2015        router = self.get_router(name_or_id)
2016        if not router:
2017            self.log.debug("Router %s not found for deleting", name_or_id)
2018            return False
2019
2020        exceptions.raise_from_response(self.network.delete(
2021            "/routers/{router_id}".format(router_id=router['id']),
2022            error_message="Error deleting router {0}".format(name_or_id)))
2023
2024        return True
2025
2026    def create_subnet(self, network_name_or_id, cidr=None, ip_version=4,
2027                      enable_dhcp=False, subnet_name=None, tenant_id=None,
2028                      allocation_pools=None,
2029                      gateway_ip=None, disable_gateway_ip=False,
2030                      dns_nameservers=None, host_routes=None,
2031                      ipv6_ra_mode=None, ipv6_address_mode=None,
2032                      prefixlen=None, use_default_subnetpool=False, **kwargs):
2033        """Create a subnet on a specified network.
2034
2035        :param string network_name_or_id:
2036           The unique name or ID of the attached network. If a non-unique
2037           name is supplied, an exception is raised.
2038        :param string cidr:
2039           The CIDR.
2040        :param int ip_version:
2041           The IP version, which is 4 or 6.
2042        :param bool enable_dhcp:
2043           Set to ``True`` if DHCP is enabled and ``False`` if disabled.
2044           Default is ``False``.
2045        :param string subnet_name:
2046           The name of the subnet.
2047        :param string tenant_id:
2048           The ID of the tenant who owns the network. Only administrative users
2049           can specify a tenant ID other than their own.
2050        :param allocation_pools:
2051           A list of dictionaries of the start and end addresses for the
2052           allocation pools. For example::
2053
2054             [
2055               {
2056                 "start": "192.168.199.2",
2057                 "end": "192.168.199.254"
2058               }
2059             ]
2060
2061        :param string gateway_ip:
2062           The gateway IP address. When you specify both allocation_pools and
2063           gateway_ip, you must ensure that the gateway IP does not overlap
2064           with the specified allocation pools.
2065        :param bool disable_gateway_ip:
2066           Set to ``True`` if gateway IP address is disabled and ``False`` if
2067           enabled. It is not allowed with gateway_ip.
2068           Default is ``False``.
2069        :param dns_nameservers:
2070           A list of DNS name servers for the subnet. For example::
2071
2072             [ "8.8.8.7", "8.8.8.8" ]
2073
2074        :param host_routes:
2075           A list of host route dictionaries for the subnet. For example::
2076
2077             [
2078               {
2079                 "destination": "0.0.0.0/0",
2080                 "nexthop": "123.456.78.9"
2081               },
2082               {
2083                 "destination": "192.168.0.0/24",
2084                 "nexthop": "192.168.0.1"
2085               }
2086             ]
2087
2088        :param string ipv6_ra_mode:
2089           IPv6 Router Advertisement mode. Valid values are: 'dhcpv6-stateful',
2090           'dhcpv6-stateless', or 'slaac'.
2091        :param string ipv6_address_mode:
2092           IPv6 address mode. Valid values are: 'dhcpv6-stateful',
2093           'dhcpv6-stateless', or 'slaac'.
2094        :param string prefixlen:
2095           The prefix length to use for subnet allocation from a subnet pool.
2096        :param bool use_default_subnetpool:
2097           Use the default subnetpool for ``ip_version`` to obtain a CIDR. It
2098           is required to pass ``None`` to the ``cidr`` argument when enabling
2099           this option.
2100        :param kwargs: Key value pairs to be passed to the Neutron API.
2101
2102        :returns: The new subnet object.
2103        :raises: OpenStackCloudException on operation error.
2104        """
2105
2106        if tenant_id is not None:
2107            filters = {'tenant_id': tenant_id}
2108        else:
2109            filters = None
2110
2111        network = self.get_network(network_name_or_id, filters)
2112        if not network:
2113            raise exc.OpenStackCloudException(
2114                "Network %s not found." % network_name_or_id)
2115
2116        if disable_gateway_ip and gateway_ip:
2117            raise exc.OpenStackCloudException(
2118                'arg:disable_gateway_ip is not allowed with arg:gateway_ip')
2119
2120        if not cidr and not use_default_subnetpool:
2121            raise exc.OpenStackCloudException(
2122                'arg:cidr is required when a subnetpool is not used')
2123
2124        if cidr and use_default_subnetpool:
2125            raise exc.OpenStackCloudException(
2126                'arg:cidr must be set to None when use_default_subnetpool == '
2127                'True')
2128
2129        # Be friendly on ip_version and allow strings
2130        if isinstance(ip_version, str):
2131            try:
2132                ip_version = int(ip_version)
2133            except ValueError:
2134                raise exc.OpenStackCloudException(
2135                    'ip_version must be an integer')
2136
2137        # The body of the neutron message for the subnet we wish to create.
2138        # This includes attributes that are required or have defaults.
2139        subnet = dict({
2140            'network_id': network['id'],
2141            'ip_version': ip_version,
2142            'enable_dhcp': enable_dhcp,
2143        }, **kwargs)
2144
2145        # Add optional attributes to the message.
2146        if cidr:
2147            subnet['cidr'] = cidr
2148        if subnet_name:
2149            subnet['name'] = subnet_name
2150        if tenant_id:
2151            subnet['tenant_id'] = tenant_id
2152        if allocation_pools:
2153            subnet['allocation_pools'] = allocation_pools
2154        if gateway_ip:
2155            subnet['gateway_ip'] = gateway_ip
2156        if disable_gateway_ip:
2157            subnet['gateway_ip'] = None
2158        if dns_nameservers:
2159            subnet['dns_nameservers'] = dns_nameservers
2160        if host_routes:
2161            subnet['host_routes'] = host_routes
2162        if ipv6_ra_mode:
2163            subnet['ipv6_ra_mode'] = ipv6_ra_mode
2164        if ipv6_address_mode:
2165            subnet['ipv6_address_mode'] = ipv6_address_mode
2166        if prefixlen:
2167            subnet['prefixlen'] = prefixlen
2168        if use_default_subnetpool:
2169            subnet['use_default_subnetpool'] = True
2170
2171        response = self.network.post("/subnets", json={"subnet": subnet})
2172
2173        return self._get_and_munchify('subnet', response)
2174
2175    def delete_subnet(self, name_or_id):
2176        """Delete a subnet.
2177
2178        If a name, instead of a unique UUID, is supplied, it is possible
2179        that we could find more than one matching subnet since names are
2180        not required to be unique. An error will be raised in this case.
2181
2182        :param name_or_id: Name or ID of the subnet being deleted.
2183
2184        :returns: True if delete succeeded, False otherwise.
2185
2186        :raises: OpenStackCloudException on operation error.
2187        """
2188        subnet = self.get_subnet(name_or_id)
2189        if not subnet:
2190            self.log.debug("Subnet %s not found for deleting", name_or_id)
2191            return False
2192
2193        exceptions.raise_from_response(self.network.delete(
2194            "/subnets/{subnet_id}".format(subnet_id=subnet['id'])))
2195        return True
2196
2197    def update_subnet(self, name_or_id, subnet_name=None, enable_dhcp=None,
2198                      gateway_ip=None, disable_gateway_ip=None,
2199                      allocation_pools=None, dns_nameservers=None,
2200                      host_routes=None):
2201        """Update an existing subnet.
2202
2203        :param string name_or_id:
2204           Name or ID of the subnet to update.
2205        :param string subnet_name:
2206           The new name of the subnet.
2207        :param bool enable_dhcp:
2208           Set to ``True`` if DHCP is enabled and ``False`` if disabled.
2209        :param string gateway_ip:
2210           The gateway IP address. When you specify both allocation_pools and
2211           gateway_ip, you must ensure that the gateway IP does not overlap
2212           with the specified allocation pools.
2213        :param bool disable_gateway_ip:
2214           Set to ``True`` if gateway IP address is disabled and ``False`` if
2215           enabled. It is not allowed with gateway_ip.
2216           Default is ``False``.
2217        :param allocation_pools:
2218           A list of dictionaries of the start and end addresses for the
2219           allocation pools. For example::
2220
2221             [
2222               {
2223                 "start": "192.168.199.2",
2224                 "end": "192.168.199.254"
2225               }
2226             ]
2227
2228        :param dns_nameservers:
2229           A list of DNS name servers for the subnet. For example::
2230
2231             [ "8.8.8.7", "8.8.8.8" ]
2232
2233        :param host_routes:
2234           A list of host route dictionaries for the subnet. For example::
2235
2236             [
2237               {
2238                 "destination": "0.0.0.0/0",
2239                 "nexthop": "123.456.78.9"
2240               },
2241               {
2242                 "destination": "192.168.0.0/24",
2243                 "nexthop": "192.168.0.1"
2244               }
2245             ]
2246
2247        :returns: The updated subnet object.
2248        :raises: OpenStackCloudException on operation error.
2249        """
2250        subnet = {}
2251        if subnet_name:
2252            subnet['name'] = subnet_name
2253        if enable_dhcp is not None:
2254            subnet['enable_dhcp'] = enable_dhcp
2255        if gateway_ip:
2256            subnet['gateway_ip'] = gateway_ip
2257        if disable_gateway_ip:
2258            subnet['gateway_ip'] = None
2259        if allocation_pools:
2260            subnet['allocation_pools'] = allocation_pools
2261        if dns_nameservers:
2262            subnet['dns_nameservers'] = dns_nameservers
2263        if host_routes:
2264            subnet['host_routes'] = host_routes
2265
2266        if not subnet:
2267            self.log.debug("No subnet data to update")
2268            return
2269
2270        if disable_gateway_ip and gateway_ip:
2271            raise exc.OpenStackCloudException(
2272                'arg:disable_gateway_ip is not allowed with arg:gateway_ip')
2273
2274        curr_subnet = self.get_subnet(name_or_id)
2275        if not curr_subnet:
2276            raise exc.OpenStackCloudException(
2277                "Subnet %s not found." % name_or_id)
2278
2279        response = self.network.put(
2280            "/subnets/{subnet_id}".format(subnet_id=curr_subnet['id']),
2281            json={"subnet": subnet})
2282        return self._get_and_munchify('subnet', response)
2283
2284    @_utils.valid_kwargs('name', 'admin_state_up', 'mac_address', 'fixed_ips',
2285                         'subnet_id', 'ip_address', 'security_groups',
2286                         'allowed_address_pairs', 'extra_dhcp_opts',
2287                         'device_owner', 'device_id', 'binding:vnic_type',
2288                         'binding:profile', 'port_security_enabled',
2289                         'qos_policy_id', 'binding:host_id')
2290    def create_port(self, network_id, **kwargs):
2291        """Create a port
2292
2293        :param network_id: The ID of the network. (Required)
2294        :param name: A symbolic name for the port. (Optional)
2295        :param admin_state_up: The administrative status of the port,
2296            which is up (true, default) or down (false). (Optional)
2297        :param mac_address: The MAC address. (Optional)
2298        :param fixed_ips: List of ip_addresses and subnet_ids. See subnet_id
2299            and ip_address. (Optional)
2300            For example::
2301
2302              [
2303                {
2304                  "ip_address": "10.29.29.13",
2305                  "subnet_id": "a78484c4-c380-4b47-85aa-21c51a2d8cbd"
2306                }, ...
2307              ]
2308        :param subnet_id: If you specify only a subnet ID, OpenStack Networking
2309            allocates an available IP from that subnet to the port. (Optional)
2310            If you specify both a subnet ID and an IP address, OpenStack
2311            Networking tries to allocate the specified address to the port.
2312        :param ip_address: If you specify both a subnet ID and an IP address,
2313            OpenStack Networking tries to allocate the specified address to
2314            the port.
2315        :param security_groups: List of security group UUIDs. (Optional)
2316        :param allowed_address_pairs: Allowed address pairs list (Optional)
2317            For example::
2318
2319              [
2320                {
2321                  "ip_address": "23.23.23.1",
2322                  "mac_address": "fa:16:3e:c4:cd:3f"
2323                }, ...
2324              ]
2325        :param extra_dhcp_opts: Extra DHCP options. (Optional).
2326            For example::
2327
2328              [
2329                {
2330                  "opt_name": "opt name1",
2331                  "opt_value": "value1"
2332                }, ...
2333              ]
2334        :param device_owner: The ID of the entity that uses this port.
2335            For example, a DHCP agent.  (Optional)
2336        :param device_id: The ID of the device that uses this port.
2337            For example, a virtual server. (Optional)
2338        :param binding vnic_type: The type of the created port. (Optional)
2339        :param port_security_enabled: The security port state created on
2340            the network. (Optional)
2341        :param qos_policy_id: The ID of the QoS policy to apply for port.
2342
2343        :returns: a ``munch.Munch`` describing the created port.
2344
2345        :raises: ``OpenStackCloudException`` on operation error.
2346        """
2347        kwargs['network_id'] = network_id
2348
2349        data = proxy._json_response(
2350            self.network.post("/ports", json={'port': kwargs}),
2351            error_message="Error creating port for network {0}".format(
2352                network_id))
2353        return self._get_and_munchify('port', data)
2354
2355    @_utils.valid_kwargs('name', 'admin_state_up', 'fixed_ips',
2356                         'security_groups', 'allowed_address_pairs',
2357                         'extra_dhcp_opts', 'device_owner', 'device_id',
2358                         'binding:vnic_type', 'binding:profile',
2359                         'port_security_enabled', 'qos_policy_id',
2360                         'binding:host_id')
2361    def update_port(self, name_or_id, **kwargs):
2362        """Update a port
2363
2364        Note: to unset an attribute use None value. To leave an attribute
2365        untouched just omit it.
2366
2367        :param name_or_id: name or ID of the port to update. (Required)
2368        :param name: A symbolic name for the port. (Optional)
2369        :param admin_state_up: The administrative status of the port,
2370            which is up (true) or down (false). (Optional)
2371        :param fixed_ips: List of ip_addresses and subnet_ids. (Optional)
2372            If you specify only a subnet ID, OpenStack Networking allocates
2373            an available IP from that subnet to the port.
2374            If you specify both a subnet ID and an IP address, OpenStack
2375            Networking tries to allocate the specified address to the port.
2376            For example::
2377
2378              [
2379                {
2380                  "ip_address": "10.29.29.13",
2381                  "subnet_id": "a78484c4-c380-4b47-85aa-21c51a2d8cbd"
2382                }, ...
2383              ]
2384        :param security_groups: List of security group UUIDs. (Optional)
2385        :param allowed_address_pairs: Allowed address pairs list (Optional)
2386            For example::
2387
2388              [
2389                {
2390                  "ip_address": "23.23.23.1",
2391                  "mac_address": "fa:16:3e:c4:cd:3f"
2392                }, ...
2393              ]
2394        :param extra_dhcp_opts: Extra DHCP options. (Optional).
2395            For example::
2396
2397              [
2398                {
2399                  "opt_name": "opt name1",
2400                  "opt_value": "value1"
2401                }, ...
2402              ]
2403        :param device_owner: The ID of the entity that uses this port.
2404            For example, a DHCP agent.  (Optional)
2405        :param device_id: The ID of the resource this port is attached to.
2406        :param binding vnic_type: The type of the created port. (Optional)
2407        :param port_security_enabled: The security port state created on
2408            the network. (Optional)
2409        :param qos_policy_id: The ID of the QoS policy to apply for port.
2410
2411        :returns: a ``munch.Munch`` describing the updated port.
2412
2413        :raises: OpenStackCloudException on operation error.
2414        """
2415        port = self.get_port(name_or_id=name_or_id)
2416        if port is None:
2417            raise exc.OpenStackCloudException(
2418                "failed to find port '{port}'".format(port=name_or_id))
2419
2420        data = proxy._json_response(
2421            self.network.put(
2422                "/ports/{port_id}".format(port_id=port['id']),
2423                json={"port": kwargs}),
2424            error_message="Error updating port {0}".format(name_or_id))
2425        return self._get_and_munchify('port', data)
2426
2427    def delete_port(self, name_or_id):
2428        """Delete a port
2429
2430        :param name_or_id: ID or name of the port to delete.
2431
2432        :returns: True if delete succeeded, False otherwise.
2433
2434        :raises: OpenStackCloudException on operation error.
2435        """
2436        port = self.get_port(name_or_id=name_or_id)
2437        if port is None:
2438            self.log.debug("Port %s not found for deleting", name_or_id)
2439            return False
2440
2441        exceptions.raise_from_response(
2442            self.network.delete(
2443                "/ports/{port_id}".format(port_id=port['id'])),
2444            error_message="Error deleting port {0}".format(name_or_id))
2445        return True
2446
2447    def _get_port_ids(self, name_or_id_list, filters=None):
2448        """
2449        Takes a list of port names or ids, retrieves ports and returns a list
2450        with port ids only.
2451
2452        :param list[str] name_or_id_list: list of port names or ids
2453        :param dict filters: optional filters
2454        :raises: SDKException on multiple matches
2455        :raises: ResourceNotFound if a port is not found
2456        :return: list of port ids
2457        :rtype: list[str]
2458        """
2459        ids_list = []
2460        for name_or_id in name_or_id_list:
2461            port = self.get_port(name_or_id, filters)
2462            if not port:
2463                raise exceptions.ResourceNotFound(
2464                    'Port {id} not found'.format(id=name_or_id))
2465            ids_list.append(port['id'])
2466        return ids_list
2467
2468    def _build_external_gateway_info(self, ext_gateway_net_id, enable_snat,
2469                                     ext_fixed_ips):
2470        info = {}
2471        if ext_gateway_net_id:
2472            info['network_id'] = ext_gateway_net_id
2473        # Only send enable_snat if it is explicitly set.
2474        if enable_snat is not None:
2475            info['enable_snat'] = enable_snat
2476        if ext_fixed_ips:
2477            info['external_fixed_ips'] = ext_fixed_ips
2478        if info:
2479            return info
2480        return None
2481