blob: 0880988b1a82584cee24d56aab28ddb52499f17f [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
16from xossynchronizer.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 Baker82565472018-08-20 11:40:03 -070058 def make_handle(self, s_tag, switch_datapath_id):
Scott Baker547dea02018-07-18 15:24:26 -070059 # 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.
Scott Baker82565472018-08-20 11:40:03 -070062 return "%d/%s" % (s_tag, switch_datapath_id)
Scott Baker547dea02018-07-18 15:24:26 -070063
64 def extract_handle(self, backend_handle):
Scott Baker82565472018-08-20 11:40:03 -070065 (s_tag, switch_datapath_id) = backend_handle.split("/",1)
Scott Baker547dea02018-07-18 15:24:26 -070066 s_tag = int(s_tag)
Scott Baker82565472018-08-20 11:40:03 -070067 return (s_tag, switch_datapath_id)
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
Scott Baker82565472018-08-20 11:40:03 -0700105 if (o.policed is None) or (o.policed < o.updated):
106 raise DeferredException("Waiting for model_policy to run on fcsi %s" % o.id)
107
Scott Bakera6c687c2018-07-16 15:08:49 -0700108 onos = self.get_fabric_onos_info(o)
109
110 si = ServiceInstance.objects.get(id=o.id)
111
Scott Baker82565472018-08-20 11:40:03 -0700112 if (o.s_tag is None):
113 raise Exception("Cannot sync FabricCrossconnectServiceInstance if s_tag is None on fcsi %s" % o.id)
Scott Bakera6c687c2018-07-16 15:08:49 -0700114
Scott Baker82565472018-08-20 11:40:03 -0700115 if (o.source_port is None):
116 raise Exception("Cannot sync FabricCrossconnectServiceInstance if source_port is None on fcsi %s" % o.id)
117
118 if (not o.switch_datapath_id):
119 raise Exception("Cannot sync FabricCrossconnectServiceInstance if switch_datapath_id is unset on fcsi %s" % o.id)
120
121 bng_mapping = self.find_bng(s_tag = o.s_tag)
Scott Bakerd443ea72018-08-07 13:50:06 -0700122 if not bng_mapping:
Scott Baker82565472018-08-20 11:40:03 -0700123 raise Exception("Unable to determine BNG port for s_tag %s" % o.s_tag)
Scott Bakerd443ea72018-08-07 13:50:06 -0700124 east_port = bng_mapping.switch_port
Scott Baker547dea02018-07-18 15:24:26 -0700125
Scott Baker82565472018-08-20 11:40:03 -0700126 data = { "deviceId": o.switch_datapath_id,
127 "vlanId": o.s_tag,
128 "ports": [ int(o.source_port), int(east_port) ] }
Scott Bakera6c687c2018-07-16 15:08:49 -0700129
Scott Baker547dea02018-07-18 15:24:26 -0700130 url = onos['url'] + '/onos/segmentrouting/xconnect'
Scott Bakera6c687c2018-07-16 15:08:49 -0700131
132 self.log.info("Sending request to ONOS", url=url, body=data)
133
134 r = requests.post(url, json=data, auth=HTTPBasicAuth(onos['user'], onos['pass']))
135
136 if r.status_code != 200:
137 raise Exception("Failed to create fabric crossconnect in ONOS: %s" % r.text)
138
Scott Baker82565472018-08-20 11:40:03 -0700139 # TODO(smbaker): If the o.backend_handle changed, then someone must have changed the
140 # FabricCrossconnectServiceInstance. If so, then we potentially need to clean up the old
141 # entry in ONOS. Furthermore, we might want to also save the two port numbers that we used,
142 # to detect someone changing those.
143
144 o.backend_handle = self.make_handle(o.s_tag, o.switch_datapath_id)
Scott Baker547dea02018-07-18 15:24:26 -0700145 o.save(update_fields=["backend_handle"])
146
Scott Bakera6c687c2018-07-16 15:08:49 -0700147 self.log.info("ONOS response", res=r.text)
148
149 def delete_record(self, o):
150 self.log.info("Deleting Fabric Crossconnect Service Instance", service_instance=o)
151
Scott Baker547dea02018-07-18 15:24:26 -0700152 if o.backend_handle:
Scott Bakera6c687c2018-07-16 15:08:49 -0700153 onos = self.get_fabric_onos_info(o)
154
Scott Baker547dea02018-07-18 15:24:26 -0700155 # backend_handle has everything we need in it to delete this entry.
Scott Baker82565472018-08-20 11:40:03 -0700156 (s_tag, switch_datapath_id) = self.extract_handle(o.backend_handle)
Scott Bakera6c687c2018-07-16 15:08:49 -0700157
Scott Baker82565472018-08-20 11:40:03 -0700158 data = { "deviceId": switch_datapath_id,
Scott Baker547dea02018-07-18 15:24:26 -0700159 "vlanId": s_tag }
Scott Bakera6c687c2018-07-16 15:08:49 -0700160
161 url = onos['url'] + '/onos/segmentrouting/xconnect'
162
163 r = requests.delete(url, json=data, auth=HTTPBasicAuth(onos['user'], onos['pass']))
164
165 if r.status_code != 204:
166 raise Exception("Failed to remove fabric crossconnect in ONOS: %s" % r.text)
167
168 self.log.info("ONOS response", res=r.text)