Matteo Scandolo | fcf842e | 2017-08-08 13:05:25 -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 | |
Rizwan Haider | e6ffdc0 | 2016-11-08 13:43:48 -0500 | [diff] [blame] | 17 | import os, sys |
| 18 | from itertools import chain |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 19 | |
Andrea Campanella | ade8848 | 2017-04-05 12:39:52 +0200 | [diff] [blame] | 20 | from synchronizers.new_base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible #if needed |
| 21 | from synchronizers.new_base.ansible_helper import run_template_ssh #if needed |
| 22 | from synchronizers.new_base.modelaccessor import * |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 23 | from xos.logger import Logger, logging |
| 24 | from synchronizers.metronetwork.providers.providerfactory import ProviderFactory |
Rizwan Haider | 65baf55 | 2016-09-28 16:47:28 -0400 | [diff] [blame] | 25 | from synchronizers.metronetwork.invokers.invokerfactory import InvokerFactory |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 26 | |
| 27 | # metronetwork will be in steps/.. |
| 28 | parentdir = os.path.join(os.path.dirname(__file__), "..") |
| 29 | sys.path.insert(0, parentdir) |
| 30 | |
| 31 | logger = Logger(level=logging.INFO) |
| 32 | |
| 33 | |
Rizwan Haider | 65baf55 | 2016-09-28 16:47:28 -0400 | [diff] [blame] | 34 | class SyncMetroNetworkSystem(SyncStep): |
| 35 | provides = [MetroNetworkSystem] |
| 36 | observes = MetroNetworkSystem |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 37 | requested_interval = 0 |
| 38 | initialized = False |
| 39 | |
| 40 | def __init__(self, **args): |
| 41 | SyncStep.__init__(self, **args) |
| 42 | |
| 43 | def fetch_pending(self, deletion=False): |
| 44 | |
| 45 | # The general idea: |
| 46 | # We do one of two things in here: |
| 47 | # 1. Full Synchronization of the DBS (XOS <-> MetroONOS) |
| 48 | # 2. Look for updates between the two stores |
| 49 | # The first thing is potentially a much bigger |
| 50 | # operation and should not happen as often |
| 51 | # |
| 52 | # The Sync operation must take into account the 'deletion' flag |
| 53 | |
| 54 | objs = [] |
| 55 | |
Rizwan Haider | 65baf55 | 2016-09-28 16:47:28 -0400 | [diff] [blame] | 56 | # Get the NetworkSystem object - if it exists it will test us |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 57 | # whether we should do a full sync or not - it all has our config |
| 58 | # information about the REST interface |
| 59 | |
Rizwan Haider | 65baf55 | 2016-09-28 16:47:28 -0400 | [diff] [blame] | 60 | metronetworksystem = self.get_metronetwork_system() |
| 61 | if not metronetworksystem: |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 62 | logger.debug("No Service configured") |
| 63 | return objs |
| 64 | |
Rizwan Haider | 65baf55 | 2016-09-28 16:47:28 -0400 | [diff] [blame] | 65 | # Check to make sure the Metro Network System is enabled |
| 66 | metronetworksystem = self.get_metronetwork_system() |
| 67 | if metronetworksystem.administrativeState == 'disabled': |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 68 | # Nothing to do |
| 69 | logger.debug("MetroService configured - state is Disabled") |
| 70 | return objs |
| 71 | |
| 72 | # The Main Loop - retrieve all the NetworkDevice objects - for each of these |
| 73 | # Apply synchronization aspects |
| 74 | networkdevices = NetworkDevice.objects.all() |
| 75 | |
| 76 | for dev in networkdevices: |
| 77 | |
| 78 | # Set up the provider |
| 79 | provider = ProviderFactory.getprovider(dev) |
| 80 | |
| 81 | # First check is for the AdminState of Disabled - do nothing |
| 82 | if dev.administrativeState == 'disabled': |
| 83 | # Nothing to do with this device |
| 84 | logger.debug("NetworkDevice %s: administrativeState set to Disabled - continuing" % dev.id) |
| 85 | |
| 86 | # Now to the main options - are we syncing - deletion portion |
| 87 | elif dev.administrativeState == 'syncrequested' and deletion is True: |
| 88 | |
| 89 | logger.info("NetworkDevice %s: administrativeState set to SyncRequested" % dev.id) |
| 90 | |
| 91 | # Kill Links |
| 92 | networklinks = provider.get_network_links_for_deletion() |
| 93 | for link in networklinks: |
| 94 | objs.append(link) |
| 95 | |
| 96 | # Kill Ports |
| 97 | allports = provider.get_network_ports_for_deletion() |
| 98 | for port in allports: |
| 99 | objs.append(port) |
| 100 | |
| 101 | logger.info("NetworkDevice %s: Deletion part of Sync completed" % dev.id) |
| 102 | dev.administrativeState = 'syncinprogress' |
| 103 | dev.save(update_fields=['administrativeState']) |
| 104 | |
| 105 | # Now to the main options - are we syncing - creation portion |
| 106 | elif dev.administrativeState == 'syncinprogress' and deletion is False: |
| 107 | |
| 108 | logger.info("NetworkDevice %s: administrativeState set to SyncRequested" % dev.id) |
| 109 | # Reload objects in the reverse order of deletion |
| 110 | |
| 111 | # Add Ports |
| 112 | networkports = provider.get_network_ports() |
| 113 | for port in networkports: |
| 114 | objs.append(port) |
| 115 | |
| 116 | # Add Links |
| 117 | networklinks = provider.get_network_links() |
| 118 | for link in networklinks: |
| 119 | objs.append(link) |
| 120 | |
| 121 | logger.info("NetworkDevice %s: Creation part of Sync completed" % dev.id) |
| 122 | dev.administrativeState = 'enabled' |
| 123 | dev.save(update_fields=['administrativeState']) |
| 124 | |
| 125 | # If we are enabled - then check for events - in either direction and sync |
| 126 | elif dev.administrativeState == 'enabled' and deletion is False: |
| 127 | logger.debug("NetworkDevice: administrativeState set to Enabled - non deletion phase") |
| 128 | |
| 129 | # This should be the 'normal running state' when we are not deleting - a few things to do in here |
| 130 | |
| 131 | # Get the changed objects from the provider - deletions are handled separately |
| 132 | eventobjs = provider.get_updated_or_created_objects() |
| 133 | for eventobj in eventobjs: |
| 134 | # Simply put in the queue for update - this will handle both new and changed objects |
| 135 | objs.append(eventobj) |
| 136 | |
| 137 | # Handle changes XOS -> ONOS |
| 138 | # Check for ConnectivityObjects that are in acticationequested state - creates to the backend |
Rizwan Haider | e6ffdc0 | 2016-11-08 13:43:48 -0500 | [diff] [blame] | 139 | p2pactivatereqs = NetworkEdgeToEdgePointConnection.objects.filter(adminstate='activationrequested') |
| 140 | mp2mpactivatereqs = NetworkMultipointToMultipointConnection.objects.filter(adminstate='activationrequested') |
| 141 | r2mpactivatereqs = NetworkEdgeToMultipointConnection.objects.filter(adminstate='activationrequested') |
| 142 | activatereqs = list(chain(p2pactivatereqs, mp2mpactivatereqs, r2mpactivatereqs)) |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 143 | for activatereq in activatereqs: |
| 144 | |
| 145 | # Call the XOS Interface to create the service |
| 146 | logger.debug("Attempting to create EdgePointToEdgePointConnectivity: %s" % activatereq.id) |
Rizwan Haider | e6ffdc0 | 2016-11-08 13:43:48 -0500 | [diff] [blame] | 147 | if (provider.create_network_connectivity(activatereq)): |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 148 | # Everyting is OK, lets let the system handle the persist |
| 149 | objs.append(activatereq) |
| 150 | else: |
| 151 | # In the case of an error we persist the state of the object directly to preserve |
| 152 | # the error code - and because that is how the base synchronizer is designed |
| 153 | activatereq.save() |
| 154 | |
| 155 | # Check for ConnectivityObjects that are in deacticationequested state - deletes to the backend |
Rizwan Haider | e6ffdc0 | 2016-11-08 13:43:48 -0500 | [diff] [blame] | 156 | p2pdeactivatereqs = NetworkEdgeToEdgePointConnection.objects.filter(adminstate='deactivationrequested') |
| 157 | mp2mpdeactivatereqs = NetworkMultipointToMultipointConnection.objects.filter(adminstate='deactivationrequested') |
| 158 | r2mpdeactivatereqs = NetworkEdgeToMultipointConnection.objects.filter(adminstate='deactivationrequested') |
| 159 | deactivatereqs = list(chain(p2pdeactivatereqs, mp2mpdeactivatereqs, r2mpdeactivatereqs)) |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 160 | for deactivatereq in deactivatereqs: |
| 161 | |
| 162 | # Call the XOS Interface to delete the service |
| 163 | logger.debug("Attempting to delete EdgePointToEdgePointConnectivity: %s" % deactivatereq.id) |
Rizwan Haider | e6ffdc0 | 2016-11-08 13:43:48 -0500 | [diff] [blame] | 164 | if provider.delete_network_connectivity(deactivatereq): |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 165 | # Everyting is OK, lets let the system handle the persist |
| 166 | objs.append(deactivatereq) |
| 167 | else: |
| 168 | # In the case of an error we persist the state of the object directly to preserve |
| 169 | # the error code - and because that is how the base synchronizer is designed |
| 170 | deactivatereq.save() |
| 171 | |
| 172 | # If we are enabled - and in our deletion pass then look for objects waiting for deletion |
| 173 | elif dev.administrativeState == 'enabled' and deletion is True: |
| 174 | logger.debug("NetworkDevice: administrativeState set to Enabled - deletion phase") |
| 175 | |
| 176 | # Any object that is simply deleted in the model gets removed automatically - the synchronizer |
| 177 | # doesn't get involved - we just need to check for deleted objects in the domain and reflect that |
| 178 | # in the model |
| 179 | # |
| 180 | # Get the deleted objects from the provider |
| 181 | eventobjs = provider.get_deleted_objects() |
| 182 | for eventobj in eventobjs: |
| 183 | # Simply put in the queue for update - this will handle both new and changed objects |
| 184 | objs.append(eventobj) |
| 185 | |
Rizwan Haider | e6ffdc0 | 2016-11-08 13:43:48 -0500 | [diff] [blame] | 186 | # Handle the case where we have deleted Eline Services from our side - if the Service is in |
| 187 | # enabled state then we call the provider, otherwise just queue it for deletion |
| 188 | elinedeletedobjs = NetworkEdgeToEdgePointConnection.deleted_objects.all() |
| 189 | for elinedeletedobj in elinedeletedobjs: |
| 190 | if elinedeletedobj.adminstate == 'enabled': |
| 191 | provider.delete_network_connectivity(elinedeletedobj) |
| 192 | # Either way queue it for deletion |
| 193 | objs.append(elinedeletedobj) |
| 194 | |
| 195 | # Handle the case where we have deleted Etree Services from our side - if the Service is in |
| 196 | # enabled state then we call the provider, otherwise just queue it for deletion |
| 197 | etreedeletedobjs = NetworkEdgeToMultipointConnection.deleted_objects.all() |
| 198 | for etreedeletedobj in etreedeletedobjs: |
| 199 | # TODO: Handle the case where its connected, we need to disconnect first |
| 200 | if etreedeletedobj.adminstate == 'enabled': |
| 201 | provider.delete_network_connectivity(etreedeletedobj) |
| 202 | # Either way queue it for deletion |
| 203 | objs.append(etreedeletedobj) |
| 204 | |
| 205 | # Handle the case where we have deleted Elan Services from our side - if the Service is in |
| 206 | # enabled state then we call the provider, otherwise just queue it for deletion |
| 207 | elandeletedobjs = NetworkMultipointToMultipointConnection.deleted_objects.all() |
| 208 | for elandeletedobj in elandeletedobjs: |
| 209 | # TODO: Handle the case where its connected, we need to disconnect first |
| 210 | if elandeletedobj.adminstate == 'enabled': |
| 211 | provider.delete_network_connectivity(elandeletedobj) |
| 212 | # Either way queue it for deletion |
| 213 | objs.append(elandeletedobj) |
| 214 | |
| 215 | # Handle the case where we have deleted VnodGlobal Services from our side - if there is |
| 216 | # an attached Eline/Etree/Elan we set that to deleted |
| 217 | vnodbloaldeletedobjs = VnodGlobalService.deleted_objects.all() |
| 218 | for vnodbloaldeletedobj in vnodbloaldeletedobjs: |
| 219 | # Check for dependent eline service |
| 220 | if vnodbloaldeletedobj.metronetworkpointtopoint is not None: |
| 221 | elineobj = vnodbloaldeletedobj.metronetworkpointtopoint |
| 222 | elineobj.deleted = True |
| 223 | objs.append(elineobj) |
| 224 | # Check for dependent elan service |
| 225 | if vnodbloaldeletedobj.metronetworkmultipoint is not None: |
| 226 | elanobj = vnodbloaldeletedobj.metronetworkmultipoint |
| 227 | elanobj.deleted = True |
| 228 | objs.append(elanobj) |
| 229 | # Check for dependent etree service |
| 230 | if vnodbloaldeletedobj.metronetworkroottomultipoint is not None: |
| 231 | etreeobj = vnodbloaldeletedobj.metronetworkroottomultipoint |
| 232 | etreeobj.deleted = True |
| 233 | objs.append(etreeobj) |
| 234 | |
| 235 | objs.append(vnodbloaldeletedobj) |
| 236 | |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 237 | # In add cases return the objects we are interested in |
| 238 | return objs |
| 239 | |
| 240 | def sync_record(self, o): |
Rizwan Haider | 65baf55 | 2016-09-28 16:47:28 -0400 | [diff] [blame] | 241 | |
| 242 | # First we call and see if there is an invoker for this object - the idea of the invoker |
| 243 | # is to wrap the save with a pre/post paradigm to handle special cases |
| 244 | # It will only exist for a subset of ojbects |
| 245 | invoker = InvokerFactory.getinvoker(o) |
| 246 | |
| 247 | # Call Pre-save on the inovker (if it exists) |
| 248 | if invoker is not None: |
| 249 | invoker.presave(o) |
| 250 | |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 251 | # Simply save the record to the DB - both updates and adds are handled the same way |
| 252 | o.save() |
| 253 | |
Rizwan Haider | 65baf55 | 2016-09-28 16:47:28 -0400 | [diff] [blame] | 254 | # Call Post-save on the inovker (if it exists) |
| 255 | if invoker is not None: |
| 256 | invoker.postsave(o) |
| 257 | |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 258 | def delete_record(self, o): |
| 259 | # Overriden to customize our behaviour - the core sync step for will remove the record directly |
| 260 | # We just log and return |
| 261 | logger.debug("deleting Object %s" % str(o), extra=o.tologdict()) |
| 262 | |
Rizwan Haider | 65baf55 | 2016-09-28 16:47:28 -0400 | [diff] [blame] | 263 | def get_metronetwork_system(self): |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 264 | # We only expect to have one of these objects in the system in the curent design |
| 265 | # So get the first element from the query |
Rizwan Haider | 65baf55 | 2016-09-28 16:47:28 -0400 | [diff] [blame] | 266 | metronetworksystem = MetroNetworkSystem.objects.all() |
| 267 | if not metronetworksystem: |
Rizwan Haider | 30b3379 | 2016-08-18 02:11:18 -0400 | [diff] [blame] | 268 | return None |
| 269 | |
Rizwan Haider | 65baf55 | 2016-09-28 16:47:28 -0400 | [diff] [blame] | 270 | return metronetworksystem[0] |