blob: ee067de6a016b2965dcc28e8951b60a20d83e1a3 [file] [log] [blame]
Scott Bakera6c687c2018-07-16 15:08:49 -07001# Copyright 2017-present Open Networking Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from synchronizers.new_base.syncstep import SyncStep, DeferredException
Scott Baker547dea02018-07-18 15:24:26 -070016from synchronizers.new_base.modelaccessor import model_accessor, FabricCrossconnectServiceInstance, ServiceInstance, BNGPortMapping
Scott Bakera6c687c2018-07-16 15:08:49 -070017
18from xosconfig import Config
19from multistructlog import create_logger
20import urllib
21import requests
22from requests.auth import HTTPBasicAuth
23
24
25class SyncFabricCrossconnectServiceInstance(SyncStep):
26 provides = [FabricCrossconnectServiceInstance]
27 log = create_logger(Config().get('logging'))
28
29 observes = FabricCrossconnectServiceInstance
30
31 @staticmethod
32 def format_url(url):
33 if 'http' in url:
34 return url
35 else:
36 return 'http://%s' % url
37
38 @staticmethod
39 def get_fabric_onos_info(si):
40
41 # get the fabric-crossconnect service
42 fabric_crossconnect = si.owner
43
44 # get the onos_fabric service
45 fabric_onos = [s.leaf_model for s in fabric_crossconnect.provider_services if "onos" in s.name.lower()]
46
47 if len(fabric_onos) == 0:
48 raise Exception('Cannot find ONOS service in provider_services of Fabric-Crossconnect')
49
50 fabric_onos = fabric_onos[0]
51
52 return {
53 'url': SyncFabricCrossconnectServiceInstance.format_url("%s:%s" % (fabric_onos.rest_hostname, fabric_onos.rest_port)),
54 'user': fabric_onos.rest_username,
55 'pass': fabric_onos.rest_password
56 }
57
Scott Baker547dea02018-07-18 15:24:26 -070058 def make_handle(self, s_tag, west_dpid):
59 # Generate a backend_handle that uniquely identifies the cross connect. ONOS doesn't provide us a handle, so
60 # we make up our own. This helps us to detect other FabricCrossconnectServiceInstance using the same
61 # entry, as well as to be able to extract the necessary information to delete the entry later.
62 return "%d/%s" % (s_tag, west_dpid)
63
64 def extract_handle(self, backend_handle):
65 (s_tag, dpid) = backend_handle.split("/",1)
66 s_tag = int(s_tag)
67 return (s_tag, dpid)
Scott Bakera6c687c2018-07-16 15:08:49 -070068
Scott Bakerd443ea72018-08-07 13:50:06 -070069 def range_matches(self, value, pattern):
70 value=int(value)
71 for this_range in pattern.split(","):
72 this_range = this_range.strip()
73 if "-" in this_range:
74 (first, last) = this_range.split("-")
75 first = int(first.strip())
76 last = int(last.strip())
77 if (value>=first) and (value<=last):
78 return True
79 elif this_range.lower()=="any":
80 return True
81 else:
82 if (value==int(this_range)):
83 return True
84 return False
85
86 def find_bng(self, s_tag):
87 # See if there's a mapping for our s-tag directly
88 bng_mappings = BNGPortMapping.objects.filter(s_tag=str(s_tag))
89 if bng_mappings:
90 return bng_mappings[0]
91
92 # TODO(smbaker): Examine miss performance, and if necessary set a flag in the save method to allow filtering
93 # of mappings based on whether they are ranges or any.
94
95 # See if there are any ranges or "any" that match
96 for bng_mapping in BNGPortMapping.objects.all():
97 if self.range_matches(s_tag, bng_mapping.s_tag):
98 return bng_mapping
99
100 return None
101
Scott Bakera6c687c2018-07-16 15:08:49 -0700102 def sync_record(self, o):
103 self.log.info("Sync'ing Fabric Crossconnect Service Instance", service_instance=o)
104
105 onos = self.get_fabric_onos_info(o)
106
107 si = ServiceInstance.objects.get(id=o.id)
108
109 s_tag = si.get_westbound_service_instance_properties("s_tag")
Scott Baker547dea02018-07-18 15:24:26 -0700110 dpid = si.get_westbound_service_instance_properties("switch_datapath_id")
Scott Bakera6c687c2018-07-16 15:08:49 -0700111 west_port = si.get_westbound_service_instance_properties("switch_port")
Scott Bakera6c687c2018-07-16 15:08:49 -0700112
Scott Bakerd443ea72018-08-07 13:50:06 -0700113 bng_mapping = self.find_bng(s_tag = s_tag)
114 if not bng_mapping:
Scott Baker547dea02018-07-18 15:24:26 -0700115 raise Exception("Unable to determine BNG port for s_tag %s" % s_tag)
Scott Bakerd443ea72018-08-07 13:50:06 -0700116 east_port = bng_mapping.switch_port
Scott Baker547dea02018-07-18 15:24:26 -0700117
118 data = { "deviceId": dpid,
Scott Bakera6c687c2018-07-16 15:08:49 -0700119 "vlanId": s_tag,
Matteo Scandolo77943112018-07-25 17:06:58 -0700120 "ports": [ int(west_port), int(east_port) ] }
Scott Bakera6c687c2018-07-16 15:08:49 -0700121
Scott Baker547dea02018-07-18 15:24:26 -0700122 url = onos['url'] + '/onos/segmentrouting/xconnect'
Scott Bakera6c687c2018-07-16 15:08:49 -0700123
124 self.log.info("Sending request to ONOS", url=url, body=data)
125
126 r = requests.post(url, json=data, auth=HTTPBasicAuth(onos['user'], onos['pass']))
127
128 if r.status_code != 200:
129 raise Exception("Failed to create fabric crossconnect in ONOS: %s" % r.text)
130
Scott Baker547dea02018-07-18 15:24:26 -0700131 o.backend_handle = self.make_handle(s_tag, dpid)
132 o.save(update_fields=["backend_handle"])
133
Scott Bakera6c687c2018-07-16 15:08:49 -0700134 self.log.info("ONOS response", res=r.text)
135
136 def delete_record(self, o):
137 self.log.info("Deleting Fabric Crossconnect Service Instance", service_instance=o)
138
Scott Baker547dea02018-07-18 15:24:26 -0700139 if o.backend_handle:
Scott Bakera6c687c2018-07-16 15:08:49 -0700140 onos = self.get_fabric_onos_info(o)
141
Scott Baker547dea02018-07-18 15:24:26 -0700142 # If some other subscriber is using the same entry, then we shouldn't delete it
143 other_subscribers = FabricCrossconnectServiceInstance.objects.filter(backend_handle=o.backend_handle)
144 other_subscribers = [x for x in other_subscribers if x.id != o.id]
145 if other_subscribers:
146 self.log.info("Other subscribers exist using same fabric crossconnect entry. Not deleting.")
147 return
Scott Bakera6c687c2018-07-16 15:08:49 -0700148
Scott Baker547dea02018-07-18 15:24:26 -0700149 # backend_handle has everything we need in it to delete this entry.
150 (s_tag, dpid) = self.extract_handle(o.backend_handle)
Scott Bakera6c687c2018-07-16 15:08:49 -0700151
Scott Baker547dea02018-07-18 15:24:26 -0700152 data = { "deviceId": dpid,
153 "vlanId": s_tag }
Scott Bakera6c687c2018-07-16 15:08:49 -0700154
155 url = onos['url'] + '/onos/segmentrouting/xconnect'
156
157 r = requests.delete(url, json=data, auth=HTTPBasicAuth(onos['user'], onos['pass']))
158
159 if r.status_code != 204:
160 raise Exception("Failed to remove fabric crossconnect in ONOS: %s" % r.text)
161
162 self.log.info("ONOS response", res=r.text)