Scott Baker | 252f692 | 2018-08-22 16:57:23 -0700 | [diff] [blame] | 1 | |
| 2 | # Copyright 2017-present Open Networking Foundation |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. |
| 15 | |
| 16 | |
| 17 | from xosapi.orm import ORMWrapper, register_convenience_wrapper |
| 18 | from xosapi.convenience.service import ORMWrapperService |
| 19 | |
| 20 | class ORMWrapperFabricCrossconnectService(ORMWrapperService): |
| 21 | |
| 22 | """ Calling convention. Assume the subscribing service does approximately (needs some checks to see |
| 23 | if the methods exist before calling them) the following in its model_policy: |
| 24 | |
| 25 | if not eastbound_service.validate_links(self): |
| 26 | eastbound_service.acquire_service_instance(self) |
| 27 | """ |
| 28 | |
| 29 | def acquire_service_instance(self, subscriber_service_instance): |
| 30 | """ Given a subscriber_service_instance: |
| 31 | 1) If there is an eligible provider_service_instance that can be used, then link to it |
| 32 | 2) Otherwise, create a new provider_service_instance and link to it. |
| 33 | """ |
| 34 | (s_tag, switch_datapath_id, source_port) = self._get_west_fields(subscriber_service_instance) |
| 35 | |
| 36 | FabricCrossconnectServiceInstance = self.stub.FabricCrossconnectServiceInstance |
| 37 | ServiceInstanceLink = self.stub.ServiceInstanceLink |
| 38 | |
| 39 | candidates = FabricCrossconnectServiceInstance.objects.filter(owner_id=self.id, |
| 40 | s_tag=s_tag, |
| 41 | switch_datapath_id=switch_datapath_id, |
| 42 | source_port=source_port) |
| 43 | |
| 44 | if candidates: |
| 45 | provider_service_instance = candidates[0] |
| 46 | else: |
| 47 | provider_service_instance = FabricCrossconnectServiceInstance(owner=self, |
| 48 | s_tag=s_tag, |
| 49 | switch_datapath_id=switch_datapath_id, |
| 50 | source_port=source_port) |
| 51 | provider_service_instance.save() |
| 52 | |
| 53 | # NOTE: Lack-of-atomicity vulnerability -- provider_service_instance could be deleted before we created the |
| 54 | # link. |
| 55 | |
| 56 | link = ServiceInstanceLink(provider_service_instance=provider_service_instance, |
| 57 | subscriber_service_instance=subscriber_service_instance) |
| 58 | link.save() |
| 59 | |
| 60 | return provider_service_instance |
| 61 | |
| 62 | def validate_links(self, subscriber_service_instance): |
| 63 | """ Validate existing links between the provider and subscriber service instances. If a valid link exists, |
| 64 | then return it. Return [] otherwise. |
| 65 | |
| 66 | As a side-effect, delete any invalid links. |
| 67 | """ |
| 68 | |
| 69 | # Short-cut -- if there are no subscriber links then we can skip getting all the properties. |
| 70 | if not subscriber_service_instance.subscribed_links.exists(): |
| 71 | return None |
| 72 | |
| 73 | (s_tag, switch_datapath_id, source_port) = self._get_west_fields(subscriber_service_instance) |
| 74 | |
| 75 | matched = [] |
| 76 | for link in subscriber_service_instance.subscribed_links.all(): |
| 77 | if link.provider_service_instance.owner.id == self.id: |
| 78 | fcsi = link.provider_service_instance.leaf_model |
| 79 | if (fcsi.s_tag == s_tag) and (fcsi.switch_datapath_id == switch_datapath_id) and \ |
| 80 | (fcsi.source_port == source_port): |
| 81 | matched.append(fcsi) |
| 82 | else: |
| 83 | link.delete() |
| 84 | return matched |
| 85 | |
| 86 | def _get_west_fields(self, subscriber_si): |
| 87 | """ _get_west_fields() |
| 88 | |
| 89 | Helper function to inspect westbound service instance for fields that will be used inside of |
| 90 | FabricCrossconnectServiceInstance. |
| 91 | """ |
| 92 | |
| 93 | s_tag = subscriber_si.get_westbound_service_instance_properties("s_tag", include_self=True) |
| 94 | switch_datapath_id = subscriber_si.get_westbound_service_instance_properties("switch_datapath_id", include_self=True) |
| 95 | source_port = subscriber_si.get_westbound_service_instance_properties("switch_port", include_self=True) |
| 96 | |
| 97 | if (s_tag is None): |
| 98 | raise Exception("Subscriber ServiceInstance %s s-tag is None" % subscriber_si.id) |
| 99 | |
| 100 | if (not switch_datapath_id): |
| 101 | raise Exception("Subscriber ServiceInstance %s switch_datapath_id is unset" % subscriber_si.id) |
| 102 | |
| 103 | if (source_port is None): |
| 104 | raise Exception("Subscriber ServiceInstance %s switch_port is None" % subscriber_si.id) |
| 105 | |
| 106 | s_tag = int(s_tag) |
| 107 | source_port = int(source_port) |
| 108 | |
| 109 | return (s_tag, switch_datapath_id, source_port) |
| 110 | |
| 111 | register_convenience_wrapper("FabricCrossconnectService", ORMWrapperFabricCrossconnectService) |