blob: 5814c198666a7ec15720cc10dafe113db443f0c8 [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
Scott Baker63afcc12019-02-01 15:41:46 -080015from xossynchronizer.steps.syncstep import SyncStep, DeferredException
Scott Bakere7b55e42019-04-01 17:18:03 -070016from xossynchronizer.modelaccessor import model_accessor, \
17 FabricCrossconnectServiceInstance, \
18 ServiceInstance, \
19 BNGPortMapping
Scott Bakera6c687c2018-07-16 15:08:49 -070020
21from xosconfig import Config
22from multistructlog import create_logger
Scott Bakera6c687c2018-07-16 15:08:49 -070023import requests
24from requests.auth import HTTPBasicAuth
25
26
27class SyncFabricCrossconnectServiceInstance(SyncStep):
28 provides = [FabricCrossconnectServiceInstance]
29 log = create_logger(Config().get('logging'))
30
31 observes = FabricCrossconnectServiceInstance
32
33 @staticmethod
34 def format_url(url):
35 if 'http' in url:
36 return url
37 else:
38 return 'http://%s' % url
39
40 @staticmethod
41 def get_fabric_onos_info(si):
42
43 # get the fabric-crossconnect service
44 fabric_crossconnect = si.owner
45
46 # get the onos_fabric service
47 fabric_onos = [s.leaf_model for s in fabric_crossconnect.provider_services if "onos" in s.name.lower()]
48
49 if len(fabric_onos) == 0:
50 raise Exception('Cannot find ONOS service in provider_services of Fabric-Crossconnect')
51
52 fabric_onos = fabric_onos[0]
53
54 return {
Scott Bakere7b55e42019-04-01 17:18:03 -070055 'url': SyncFabricCrossconnectServiceInstance.format_url(
56 "%s:%s" %
57 (fabric_onos.rest_hostname,
58 fabric_onos.rest_port)),
Scott Bakera6c687c2018-07-16 15:08:49 -070059 'user': fabric_onos.rest_username,
Scott Bakere7b55e42019-04-01 17:18:03 -070060 'pass': fabric_onos.rest_password}
Scott Bakera6c687c2018-07-16 15:08:49 -070061
Scott Baker82565472018-08-20 11:40:03 -070062 def make_handle(self, s_tag, switch_datapath_id):
Scott Baker547dea02018-07-18 15:24:26 -070063 # Generate a backend_handle that uniquely identifies the cross connect. ONOS doesn't provide us a handle, so
64 # we make up our own. This helps us to detect other FabricCrossconnectServiceInstance using the same
65 # entry, as well as to be able to extract the necessary information to delete the entry later.
Scott Baker82565472018-08-20 11:40:03 -070066 return "%d/%s" % (s_tag, switch_datapath_id)
Scott Baker547dea02018-07-18 15:24:26 -070067
68 def extract_handle(self, backend_handle):
Scott Bakere7b55e42019-04-01 17:18:03 -070069 (s_tag, switch_datapath_id) = backend_handle.split("/", 1)
Scott Baker547dea02018-07-18 15:24:26 -070070 s_tag = int(s_tag)
Scott Baker82565472018-08-20 11:40:03 -070071 return (s_tag, switch_datapath_id)
Scott Bakera6c687c2018-07-16 15:08:49 -070072
Scott Bakerd443ea72018-08-07 13:50:06 -070073 def range_matches(self, value, pattern):
Scott Bakere7b55e42019-04-01 17:18:03 -070074 value = int(value)
Scott Bakerd443ea72018-08-07 13:50:06 -070075 for this_range in pattern.split(","):
76 this_range = this_range.strip()
77 if "-" in this_range:
78 (first, last) = this_range.split("-")
79 first = int(first.strip())
80 last = int(last.strip())
Scott Bakere7b55e42019-04-01 17:18:03 -070081 if (value >= first) and (value <= last):
Scott Bakerd443ea72018-08-07 13:50:06 -070082 return True
Scott Bakere7b55e42019-04-01 17:18:03 -070083 elif this_range.lower() == "any":
Scott Bakerd443ea72018-08-07 13:50:06 -070084 return True
85 else:
Scott Bakere7b55e42019-04-01 17:18:03 -070086 if (value == int(this_range)):
Scott Bakerd443ea72018-08-07 13:50:06 -070087 return True
88 return False
89
90 def find_bng(self, s_tag):
91 # See if there's a mapping for our s-tag directly
92 bng_mappings = BNGPortMapping.objects.filter(s_tag=str(s_tag))
93 if bng_mappings:
94 return bng_mappings[0]
95
96 # TODO(smbaker): Examine miss performance, and if necessary set a flag in the save method to allow filtering
97 # of mappings based on whether they are ranges or any.
98
99 # See if there are any ranges or "any" that match
100 for bng_mapping in BNGPortMapping.objects.all():
101 if self.range_matches(s_tag, bng_mapping.s_tag):
Scott Bakere7b55e42019-04-01 17:18:03 -0700102 return bng_mapping
Scott Bakerd443ea72018-08-07 13:50:06 -0700103
104 return None
105
Scott Bakera6c687c2018-07-16 15:08:49 -0700106 def sync_record(self, o):
107 self.log.info("Sync'ing Fabric Crossconnect Service Instance", service_instance=o)
108
Scott Baker82565472018-08-20 11:40:03 -0700109 if (o.policed is None) or (o.policed < o.updated):
110 raise DeferredException("Waiting for model_policy to run on fcsi %s" % o.id)
111
Scott Bakera6c687c2018-07-16 15:08:49 -0700112 onos = self.get_fabric_onos_info(o)
113
Scott Bakere7b55e42019-04-01 17:18:03 -0700114 ServiceInstance.objects.get(id=o.id)
Scott Bakera6c687c2018-07-16 15:08:49 -0700115
Scott Baker82565472018-08-20 11:40:03 -0700116 if (o.s_tag is None):
117 raise Exception("Cannot sync FabricCrossconnectServiceInstance if s_tag is None on fcsi %s" % o.id)
Scott Bakera6c687c2018-07-16 15:08:49 -0700118
Scott Baker82565472018-08-20 11:40:03 -0700119 if (o.source_port is None):
120 raise Exception("Cannot sync FabricCrossconnectServiceInstance if source_port is None on fcsi %s" % o.id)
121
122 if (not o.switch_datapath_id):
Scott Bakere7b55e42019-04-01 17:18:03 -0700123 raise Exception(
124 "Cannot sync FabricCrossconnectServiceInstance if switch_datapath_id is unset on fcsi %s" %
125 o.id)
Scott Baker82565472018-08-20 11:40:03 -0700126
Scott Bakere7b55e42019-04-01 17:18:03 -0700127 bng_mapping = self.find_bng(s_tag=o.s_tag)
Scott Bakerd443ea72018-08-07 13:50:06 -0700128 if not bng_mapping:
Scott Baker82565472018-08-20 11:40:03 -0700129 raise Exception("Unable to determine BNG port for s_tag %s" % o.s_tag)
Scott Bakerd443ea72018-08-07 13:50:06 -0700130 east_port = bng_mapping.switch_port
Scott Baker547dea02018-07-18 15:24:26 -0700131
Scott Bakere7b55e42019-04-01 17:18:03 -0700132 data = {"deviceId": o.switch_datapath_id,
133 "vlanId": o.s_tag,
134 "ports": [int(o.source_port), int(east_port)]}
Scott Bakera6c687c2018-07-16 15:08:49 -0700135
Scott Baker547dea02018-07-18 15:24:26 -0700136 url = onos['url'] + '/onos/segmentrouting/xconnect'
Scott Bakera6c687c2018-07-16 15:08:49 -0700137
138 self.log.info("Sending request to ONOS", url=url, body=data)
139
140 r = requests.post(url, json=data, auth=HTTPBasicAuth(onos['user'], onos['pass']))
141
142 if r.status_code != 200:
143 raise Exception("Failed to create fabric crossconnect in ONOS: %s" % r.text)
144
Scott Baker82565472018-08-20 11:40:03 -0700145 # TODO(smbaker): If the o.backend_handle changed, then someone must have changed the
146 # FabricCrossconnectServiceInstance. If so, then we potentially need to clean up the old
147 # entry in ONOS. Furthermore, we might want to also save the two port numbers that we used,
148 # to detect someone changing those.
149
150 o.backend_handle = self.make_handle(o.s_tag, o.switch_datapath_id)
Andy Bavierc4f3a982019-02-08 14:55:52 -0700151 o.save_changed_fields()
Scott Baker547dea02018-07-18 15:24:26 -0700152
Scott Bakera6c687c2018-07-16 15:08:49 -0700153 self.log.info("ONOS response", res=r.text)
154
155 def delete_record(self, o):
156 self.log.info("Deleting Fabric Crossconnect Service Instance", service_instance=o)
157
Scott Baker547dea02018-07-18 15:24:26 -0700158 if o.backend_handle:
Scott Bakera6c687c2018-07-16 15:08:49 -0700159 onos = self.get_fabric_onos_info(o)
160
Scott Baker547dea02018-07-18 15:24:26 -0700161 # backend_handle has everything we need in it to delete this entry.
Scott Baker82565472018-08-20 11:40:03 -0700162 (s_tag, switch_datapath_id) = self.extract_handle(o.backend_handle)
Scott Bakera6c687c2018-07-16 15:08:49 -0700163
Scott Bakere7b55e42019-04-01 17:18:03 -0700164 data = {"deviceId": switch_datapath_id,
165 "vlanId": s_tag}
Scott Bakera6c687c2018-07-16 15:08:49 -0700166
167 url = onos['url'] + '/onos/segmentrouting/xconnect'
168
169 r = requests.delete(url, json=data, auth=HTTPBasicAuth(onos['user'], onos['pass']))
170
171 if r.status_code != 204:
172 raise Exception("Failed to remove fabric crossconnect in ONOS: %s" % r.text)
173
174 self.log.info("ONOS response", res=r.text)