class Monitor(threading.Thread):
    """
    Thread responsible for monitoring EPG-to-Contract relations.
    """
    def __init__(self, cdb):
        threading.Thread.__init__(self)
        self.cdb = cdb
        self._monitor_frequency = 1
        self._exit = False
        self._relation_subscriptions = []
        self._inheritance_tag_subscriptions = []
        self._subnet_subscriptions = []
        self._relations = RelationDB()
        self._inheritance_tags = TagDB()
        self._subnets = SubnetDB()
        self._old_relations = {}
        self.apic = None

    def exit(self):
        """
        Indicate that the thread should exit.
        """
        self._exit = True

    def subscribe(self, policy):
        query_url = ''
        epg = policy.epg
        epg_data = (epg.tenant, epg.epg_container_name, epg.name)
        if epg.epg_container_type == 'app':
            query_url = '/api/mo/uni/tn-%s/ap-%s/epg-%s.json' % epg_data
        elif epg.epg_container_type == 'l3out':
            subnet_query_url = '/api/mo/uni/tn-%s/out-%s/instP-%s.json' % epg_data
            subnet_query_url += '?query-target=subtree&target-subtree-class=l3extSubnet'
            subnet_query_url += '&subscription=yes'
            if subnet_query_url not in self._subnet_subscriptions:
                self.apic.subscribe(subnet_query_url)
                self._subnet_subscriptions.append(subnet_query_url)
            query_url = '/api/mo/uni/tn-%s/out-%s/instP-%s.json' % epg_data
        elif epg.epg_container_type == 'l2out':
            query_url = '/api/mo/uni/tn-%s/l2out-%s/instP-%s.json' % epg_data
        query_url += '?query-target=subtree&target-subtree-class=fvRsProv,fvRsCons,fvRsProtBy,fvRsConsIf'
        query_url += '&subscription=yes'
        if query_url not in self._relation_subscriptions:
            self.apic.subscribe(query_url)
            self._relation_subscriptions.append(query_url)

    def connect_to_apic(self):
        logging.debug('Connecting to APIC...')
        # Connect to APIC
        apic_config = self.cdb.get_apic_config()
        url = apic_config.ip_address
        if apic_config.use_https:
            url = 'https://' + url
        else:
            url = 'http://' + url
        if self.apic is not None:
            logging.debug('APIC is previously connected')
        self.apic = Session(url, apic_config.user_name, apic_config.password)
        resp = self.apic.login()

        # TODO: need to clear out the databases first
        assert len(self._inheritance_tags.db) == 0

        # Get all of the subnets
        query_url = '/api/mo/uni.json?query-target=subtree&target-subtree-class=l3extSubnet'
        subnets = self.apic.get(query_url)
        for subnet in subnets.json()['imdata']:
            subnet_event = SubnetEvent(subnet)
            self._subnets.store_subnet_event(subnet_event)

        # Get all of the inherited relations
        tag_query_url = '/api/class/tagInst.json?query-target-filter=wcard(tagInst.name,"inherited:")'
        tags = self.apic.get(tag_query_url)
        for tag in tags.json()['imdata']:
            tag_event = TagEvent(tag)
            # Create a database entry for the inherited relations
            self._inheritance_tags.store_tag(tag_event)
        self._old_relations = self._inheritance_tags.get_relations()

        # Get all of the relations. We need this to track relations that are already present
        # i.e. configured but not through inheritance so that we can tell the difference
        query_url = '/api/mo/uni.json?query-target=subtree&target-subtree-class=fvRsProv,fvRsCons,fvRsProtBy,fvRsConsIf'
        relations = self.apic.get(query_url)
        for relation in relations.json()['imdata']:
            # Skip any in-band and out-of-band interfaces
            if '/mgmtp-' in relation[relation.keys()[0]]['attributes']['dn']:
                continue
            self._relations.store_relation(RelationEvent(relation))

        # Get all of the policies that are inherited from but not inheriting
        parent_only_policies = []
        for policy in self.cdb.get_inheritance_policies():
            inherited_from = False
            if policy.allowed and not policy.enabled:
                for child_policy in self.cdb.get_inheritance_policies():
                    if child_policy.has_inherit_from(
                    ) and child_policy.inherit_from == policy.epg:
                        inherited_from = True
            if inherited_from:
                parent_only_policies.append(policy)

        # Issue all of the subscriptions
        for policy in self.cdb.get_inheritance_policies():
            self.subscribe(policy)
        for parent_only_policy in parent_only_policies:
            self.subscribe(parent_only_policy)
        tag_query_url += '&subscription=yes'
        self.apic.subscribe(tag_query_url)
        self._inheritance_tag_subscriptions.append(tag_query_url)

    def _does_tenant_have_contract(self,
                                   tenant_name,
                                   contract_name,
                                   contract_type='brc'):
        logging.debug('tenant: %s contract: %s contract_type: %s', tenant_name,
                      contract_name, contract_type)
        query_url = '/api/mo/uni/tn-%s/%s-%s.json' % (
            tenant_name, contract_type, contract_name)
        resp = self.apic.get(query_url)
        return resp.ok and int(resp.json()['totalCount']) > 0

    def does_tenant_have_contract_if(self, tenant_name, contract_if_name):
        return self._does_tenant_have_contract(tenant_name,
                                               contract_if_name,
                                               contract_type='cif')

    def does_tenant_have_contract(self, tenant_name, contract_name):
        return self._does_tenant_have_contract(tenant_name, contract_name)

    def _add_inherited_relation(self, tenants, epg, relation, deleted=False):
        tenant_found = False

        # Find the tenant. Add if necessary
        for tenant in tenants:
            if tenant.name == epg.tenant:
                tenant_found = True
                break
        if not tenant_found:
            tenant = Tenant(epg.tenant)
            tenants.append(tenant)

        # Find the EPG Container. Add if necessary
        if epg.is_l3out():
            epg_container_class = OutsideL3
        else:
            epg_container_class = AppProfile
        epg_containers = tenant.get_children(only_class=epg_container_class)
        epg_container_found = False
        for epg_container in epg_containers:
            if epg_container.name == epg.epg_container_name:
                epg_container_found = True
                break
        if not epg_container_found:
            epg_container = epg_container_class(epg.epg_container_name, tenant)

        # Find the EPG. Add if necessary
        if epg.is_l3out():
            epg_class = OutsideEPG
        else:
            epg_class = EPG
        epgs = tenant.get_children(only_class=epg_class)
        epg_found = False
        for tenant_epg in epgs:
            if tenant_epg.name == epg.name:
                epg_found = True
                break
        if not epg_found:
            tenant_epg = epg_class(epg.name, epg_container)

        # Add the relation
        (relation_type, relation_name) = relation
        if relation_type == 'fvRsProv':
            contract = Contract(relation_name, tenant)
            tenant_epg.provide(contract)
            if deleted:
                tenant_epg.provide(contract)
                tenant_epg.dont_provide(contract)
        elif relation_type == 'fvRsCons':
            contract = Contract(relation_name, tenant)
            tenant_epg.consume(contract)
            if deleted:
                tenant_epg.consume(contract)
                tenant_epg.dont_consume(contract)
        elif relation_type == 'fvRsConsIf':
            contract_interface = ContractInterface(relation_name, tenant)
            tenant_epg.consume_cif(contract_interface)
            if deleted:
                tenant_epg.consume_cif(contract_interface)
                tenant_epg.dont_consume_cif(contract_interface)
        elif relation_type == 'fvRsProtBy':
            taboo = Taboo(relation_name, tenant)
            tenant_epg.protect(taboo)
            if deleted:
                tenant_epg.protect(taboo)
                tenant_epg.dont_protect(taboo)
        tenant_epg.add_tag('inherited:%s:%s' % (relation_type, relation_name))
        if deleted:
            tenant_epg.delete_tag('inherited:%s:%s' %
                                  (relation_type, relation_name))
        return tenants

    def add_inherited_relation(self, tenants, epg, relation):
        return self._add_inherited_relation(tenants, epg, relation)

    def remove_inherited_relation(self, tenants, epg, relation):
        return self._add_inherited_relation(tenants,
                                            epg,
                                            relation,
                                            deleted=True)

    def _calculate_relations_for_l3out_policy(self, inheritance_policy):
        logging.debug('_calculate_relations_for_l3out_policy: %s',
                      inheritance_policy)
        # Get all of the EPGs covering this policy's EPG's subnets
        covering_epgs = self._subnets.get_all_covering_epgs(
            inheritance_policy.epg)

        # Remove any EPGs that are not allowed to be inherited
        for covering_epg in covering_epgs:
            if not self.cdb.is_inheritance_allowed(covering_epg):
                covering_epgs.remove(covering_epg)

        # Get all of the relations for the remaining covering EPGs
        relations = []
        for covering_epg in covering_epgs:
            epg_relations = self._relations.get_relations_for_epg(covering_epg)
            for epg_relation in epg_relations:
                if epg_relation not in relations:
                    relations.append(epg_relation)

        logging.debug('Relations to be inherited: %s', relations)

        # Need to add any configured relations
        configured_relations = self._relations.get_relations_for_epg(
            inheritance_policy.epg)
        for configured_relation in configured_relations:
            if configured_relation not in relations:
                logging.debug('Adding configured relation: %s',
                              configured_relation)
                if not self._inheritance_tags.is_inherited(
                        inheritance_policy.epg, configured_relation):
                    relations.append(configured_relation)
        return relations

    def _calculate_relations_for_app_policy(self, inheritance_policy):
        logging.debug('policy: %s', inheritance_policy)
        relations = []
        # Check if this policy is even inheritance enabled
        if not inheritance_policy.has_inherit_from():
            logging.warning('EPG is not inheriting from a parent EPG')
            return relations
        # Get the EPG that this policy is inheriting from
        parent_epg = inheritance_policy.inherit_from
        # Is inheritance allowed on that EPG ?
        if not self.cdb.is_inheritance_allowed(parent_epg):
            logging.warning('Parent EPG policy does not allow inheritance')
            return relations
        # Get the relations belonging to that EPG
        return self._relations.get_relations_for_epg(parent_epg)

    def calculate_relations(self):
        relations = {}
        for inheritance_policy in self.cdb.get_inheritance_policies():
            if not inheritance_policy.enabled:
                continue
            if inheritance_policy.epg.is_l3out():
                epg_relations = self._calculate_relations_for_l3out_policy(
                    inheritance_policy)
            else:
                # TODO: may eventually need to process l2out
                epg_relations = self._calculate_relations_for_app_policy(
                    inheritance_policy)
            relations[inheritance_policy.epg.get_json()] = epg_relations
        return relations

    def process_relation_event(self, event):
        logging.debug('process_event EVENT: %s', event.event)
        self._relations.store_relation(event)

    def process_subnet_event(self, event):
        logging.debug('Received subnet event: %s', event)
        # Store the subnet in the SubnetDB
        self._subnets.store_subnet_event(event)

    def process_inheritance_tag_event(self, event):
        logging.debug('Received subnet event: %s', event)
        # Store the tag in the TagDB
        self._inheritance_tags.store_tag(event)

    def _process_events(self, old_relations):
        while self.apic is None:
            pass

        if old_relations is None:
            old_relations = self._inheritance_tags.get_relations()

        # Check for any tag events
        for subscription in self._inheritance_tag_subscriptions:
            while self.apic.has_events(subscription):
                event = TagEvent(
                    self.apic.get_event(subscription)['imdata'][0])
                self.process_inheritance_tag_event(event)

        # Check for relation events
        for subscription in self._relation_subscriptions:
            while self.apic.has_events(subscription):
                event = RelationEvent(
                    self.apic.get_event(subscription)['imdata'][0])
                self.process_relation_event(event)

        # Check for subnet events
        for subscription in self._subnet_subscriptions:
            while self.apic.has_events(subscription):
                event = SubnetEvent(
                    self.apic.get_event(subscription)['imdata'][0])
                self.process_subnet_event(event)

        # Calculate the new set of relations
        new_relations = self.calculate_relations()

        for old_epg in old_relations:
            logging.debug(old_epg)
        for new_epg in new_relations:
            logging.debug(new_epg)

        # Compare the old and the new relations for changes
        tenants = []
        for new_epg in new_relations:
            if new_epg not in old_relations:
                # New EPG, so we need to add all of the relations
                for new_relation in new_relations[new_epg]:
                    if self._inheritance_tags.is_inherited(
                            EPGPolicy(json.loads(new_epg)), new_relation):
                        # If it's inherited, but we don't have the relation. We are likely coming up and not populated
                        # the old_relations yet
                        pass
                    # If just configured, we will have a relationDB entry. Otherwise, we need to inherit it
                    if self._relations.has_relation_for_epg(
                            EPGPolicy(json.loads(new_epg)), new_relation):
                        continue
                    tenants = self.add_inherited_relation(
                        tenants, EPGPolicy(json.loads(new_epg)), new_relation)
            else:
                # Handle any new added relations
                for new_relation in new_relations[new_epg]:
                    if new_relation not in old_relations[new_epg]:
                        if self._relations.has_relation_for_epg(
                                EPGPolicy(json.loads(new_epg)), new_relation):
                            continue
                        tenants = self.add_inherited_relation(
                            tenants, EPGPolicy(json.loads(new_epg)),
                            new_relation)
                # Handle any deleted relations
                for old_relation in old_relations[new_epg]:
                    if old_relation not in new_relations[new_epg]:
                        if self._inheritance_tags.is_inherited(
                                EPGPolicy(json.loads(new_epg)), old_relation):
                            tenants = self.remove_inherited_relation(
                                tenants, EPGPolicy(json.loads(new_epg)),
                                old_relation)
                        else:
                            # Must have been configured and manually deleted
                            pass
        for old_epg in old_relations:
            if old_epg not in new_relations:
                for old_relation in old_relations[old_epg]:
                    if self._inheritance_tags.is_inherited(
                            EPGPolicy(json.loads(old_epg)), old_relation):
                        tenants = self.remove_inherited_relation(
                            tenants, EPGPolicy(json.loads(old_epg)),
                            old_relation)

        # Push the necessary config to the APIC
        for tenant in tenants:
            tenant_json = tenant.get_json()
            # Check that the tenant actually has the contracts since they may actually be tenant common contracts.
            # If tenant common is used, we need to clean up the tenant JSON to not create an empty contract within
            # this tenant
            for child in tenant_json['fvTenant']['children']:
                if 'vzBrCP' in child:
                    if not self.does_tenant_have_contract(
                            tenant.name,
                            child['vzBrCP']['attributes']['name']):
                        tenant_json['fvTenant']['children'].remove(child)
                elif 'vzCPIf' in child:
                    if not self.does_tenant_have_contract_if(
                            tenant.name,
                            child['vzCPIf']['attributes']['name']):
                        tenant_json['fvTenant']['children'].remove(child)
            logging.debug('Pushing tenant configuration to the APIC: %s',
                          tenant_json)
            resp = self.apic.push_to_apic(tenant.get_url(), tenant_json)
            if resp.ok:
                logging.debug('Pushed to APIC successfully')
            else:
                logging.error('Error pushing to APIC', resp.text)
        return new_relations

    def run(self):
        loop_count = 0
        accelerated_cleanup_done = False
        while not self._exit:
            time.sleep(self._monitor_frequency)
            self._old_relations = self._process_events(self._old_relations)
            urls.append(url)
        except:
            error("Error creating subscription for tenant '%s'" % tn.name)

    # Also, subscribe to the Tenant class so we can create new subscriptions
    # if new tenants get created.
    tn_url = "/api/class/fvTenant.json?subscription=yes"
    session.subscribe(tn_url, only_new=True)

    # Now we loop forever, waiting for any events
    output("Waiting for events...")
    while(True):
        
        # Check status of existing tenants
        for url in urls:
            if session.has_events(url):
                event_data = session.get_event(url)
                do_something(event_data)
        
        # Check for new tenants (if any tenants get added, we create a new
        # subscription; if they get deleted, we remove the existing one).
        if session.has_events(tn_url):
            tn = session.get_event(tn_url)
            tn_dn = tn['imdata'][0]['fvTenant']['attributes']['dn']
            url = "/api/mo/%s.json?query-target=subtree&subscription=yes" % tn_dn
            if tn['imdata'][0]['fvTenant']['attributes']['status']=="created":
                debug("Subscribing to '%s'" % url)
                session.subscribe(url, only_new=True)
                urls.append(url)
            elif tn['imdata'][0]['fvTenant']['attributes']['status']=="deleted":
                debug("Unsubscribing from '%s'" % url)
Example #3
0
class Monitor(threading.Thread):
    """
    Thread responsible for monitoring EPG-to-Contract relations.
    """

    def __init__(self, cdb):
        threading.Thread.__init__(self)
        self.cdb = cdb
        self._monitor_frequency = 1
        self._exit = False
        self._relation_subscriptions = []
        self._inheritance_tag_subscriptions = []
        self._subnet_subscriptions = []
        self._relations = RelationDB()
        self._inheritance_tags = TagDB()
        self._subnets = SubnetDB()
        self._old_relations = {}
        self.apic = None

    def exit(self):
        """
        Indicate that the thread should exit.
        """
        self._exit = True

    def subscribe(self, policy):
        query_url = ""
        epg = policy.epg
        epg_data = (epg.tenant, epg.epg_container_name, epg.name)
        if epg.epg_container_type == "app":
            query_url = "/api/mo/uni/tn-%s/ap-%s/epg-%s.json" % epg_data
        elif epg.epg_container_type == "l3out":
            subnet_query_url = "/api/mo/uni/tn-%s/out-%s/instP-%s.json" % epg_data
            subnet_query_url += "?query-target=subtree&target-subtree-class=l3extSubnet"
            subnet_query_url += "&subscription=yes"
            if subnet_query_url not in self._subnet_subscriptions:
                self.apic.subscribe(subnet_query_url)
                self._subnet_subscriptions.append(subnet_query_url)
            query_url = "/api/mo/uni/tn-%s/out-%s/instP-%s.json" % epg_data
        elif epg.epg_container_type == "l2out":
            query_url = "/api/mo/uni/tn-%s/l2out-%s/instP-%s.json" % epg_data
        query_url += "?query-target=subtree&target-subtree-class=fvRsProv,fvRsCons,fvRsProtBy,fvRsConsIf"
        query_url += "&subscription=yes"
        if query_url not in self._relation_subscriptions:
            self.apic.subscribe(query_url)
            self._relation_subscriptions.append(query_url)

    def connect_to_apic(self):
        logging.debug("Connecting to APIC...")
        # Connect to APIC
        apic_config = self.cdb.get_apic_config()
        url = apic_config.ip_address
        if apic_config.use_https:
            url = "https://" + url
        else:
            url = "http://" + url
        if self.apic is not None:
            logging.debug("APIC is previously connected")
        self.apic = Session(url, apic_config.user_name, apic_config.password)
        resp = self.apic.login()

        # TODO: need to clear out the databases first
        assert len(self._inheritance_tags.db) == 0

        # Get all of the subnets
        query_url = "/api/mo/uni.json?query-target=subtree&target-subtree-class=l3extSubnet"
        subnets = self.apic.get(query_url)
        for subnet in subnets.json()["imdata"]:
            subnet_event = SubnetEvent(subnet)
            self._subnets.store_subnet_event(subnet_event)

        # Get all of the inherited relations
        tag_query_url = '/api/class/tagInst.json?query-target-filter=wcard(tagInst.name,"inherited:")'
        tags = self.apic.get(tag_query_url)
        for tag in tags.json()["imdata"]:
            tag_event = TagEvent(tag)
            # Create a database entry for the inherited relations
            self._inheritance_tags.store_tag(tag_event)
        self._old_relations = self._inheritance_tags.get_relations()

        # Get all of the relations. We need this to track relations that are already present
        # i.e. configured but not through inheritance so that we can tell the difference
        query_url = "/api/mo/uni.json?query-target=subtree&target-subtree-class=fvRsProv,fvRsCons,fvRsProtBy,fvRsConsIf"
        relations = self.apic.get(query_url)
        for relation in relations.json()["imdata"]:
            # Skip any in-band and out-of-band interfaces
            if "/mgmtp-" in relation[relation.keys()[0]]["attributes"]["dn"]:
                continue
            self._relations.store_relation(RelationEvent(relation))

        # Get all of the policies that are inherited from but not inheriting
        parent_only_policies = []
        for policy in self.cdb.get_inheritance_policies():
            inherited_from = False
            if policy.allowed and not policy.enabled:
                for child_policy in self.cdb.get_inheritance_policies():
                    if child_policy.has_inherit_from() and child_policy.inherit_from == policy.epg:
                        inherited_from = True
            if inherited_from:
                parent_only_policies.append(policy)

        # Issue all of the subscriptions
        for policy in self.cdb.get_inheritance_policies():
            self.subscribe(policy)
        for parent_only_policy in parent_only_policies:
            self.subscribe(parent_only_policy)
        tag_query_url += "&subscription=yes"
        self.apic.subscribe(tag_query_url)
        self._inheritance_tag_subscriptions.append(tag_query_url)

    def _does_tenant_have_contract(self, tenant_name, contract_name, contract_type="brc"):
        logging.debug("tenant: %s contract: %s contract_type: %s", tenant_name, contract_name, contract_type)
        query_url = "/api/mo/uni/tn-%s/%s-%s.json" % (tenant_name, contract_type, contract_name)
        resp = self.apic.get(query_url)
        return resp.ok and int(resp.json()["totalCount"]) > 0

    def does_tenant_have_contract_if(self, tenant_name, contract_if_name):
        return self._does_tenant_have_contract(tenant_name, contract_if_name, contract_type="cif")

    def does_tenant_have_contract(self, tenant_name, contract_name):
        return self._does_tenant_have_contract(tenant_name, contract_name)

    def _add_inherited_relation(self, tenants, epg, relation, deleted=False):
        tenant_found = False

        # Find the tenant. Add if necessary
        for tenant in tenants:
            if tenant.name == epg.tenant:
                tenant_found = True
                break
        if not tenant_found:
            tenant = Tenant(epg.tenant)
            tenants.append(tenant)

        # Find the EPG Container. Add if necessary
        if epg.is_l3out():
            epg_container_class = OutsideL3
        else:
            epg_container_class = AppProfile
        epg_containers = tenant.get_children(only_class=epg_container_class)
        epg_container_found = False
        for epg_container in epg_containers:
            if epg_container.name == epg.epg_container_name:
                epg_container_found = True
                break
        if not epg_container_found:
            epg_container = epg_container_class(epg.epg_container_name, tenant)

        # Find the EPG. Add if necessary
        if epg.is_l3out():
            epg_class = OutsideEPG
        else:
            epg_class = EPG
        epgs = tenant.get_children(only_class=epg_class)
        epg_found = False
        for tenant_epg in epgs:
            if tenant_epg.name == epg.name:
                epg_found = True
                break
        if not epg_found:
            tenant_epg = epg_class(epg.name, epg_container)

        # Add the relation
        (relation_type, relation_name) = relation
        if relation_type == "fvRsProv":
            contract = Contract(relation_name, tenant)
            tenant_epg.provide(contract)
            if deleted:
                tenant_epg.provide(contract)
                tenant_epg.dont_provide(contract)
        elif relation_type == "fvRsCons":
            contract = Contract(relation_name, tenant)
            tenant_epg.consume(contract)
            if deleted:
                tenant_epg.consume(contract)
                tenant_epg.dont_consume(contract)
        elif relation_type == "fvRsConsIf":
            contract_interface = ContractInterface(relation_name, tenant)
            tenant_epg.consume_cif(contract_interface)
            if deleted:
                tenant_epg.consume_cif(contract_interface)
                tenant_epg.dont_consume_cif(contract_interface)
        elif relation_type == "fvRsProtBy":
            taboo = Taboo(relation_name, tenant)
            tenant_epg.protect(taboo)
            if deleted:
                tenant_epg.protect(taboo)
                tenant_epg.dont_protect(taboo)
        tenant_epg.add_tag("inherited:%s:%s" % (relation_type, relation_name))
        if deleted:
            tenant_epg.delete_tag("inherited:%s:%s" % (relation_type, relation_name))
        return tenants

    def add_inherited_relation(self, tenants, epg, relation):
        return self._add_inherited_relation(tenants, epg, relation)

    def remove_inherited_relation(self, tenants, epg, relation):
        return self._add_inherited_relation(tenants, epg, relation, deleted=True)

    def _calculate_relations_for_l3out_policy(self, inheritance_policy):
        logging.debug("_calculate_relations_for_l3out_policy: %s", inheritance_policy)
        # Get all of the EPGs covering this policy's EPG's subnets
        covering_epgs = self._subnets.get_all_covering_epgs(inheritance_policy.epg)

        # Remove any EPGs that are not allowed to be inherited
        for covering_epg in covering_epgs:
            if not self.cdb.is_inheritance_allowed(covering_epg):
                covering_epgs.remove(covering_epg)

        # Get all of the relations for the remaining covering EPGs
        relations = []
        for covering_epg in covering_epgs:
            epg_relations = self._relations.get_relations_for_epg(covering_epg)
            for epg_relation in epg_relations:
                if epg_relation not in relations:
                    relations.append(epg_relation)

        logging.debug("Relations to be inherited: %s", relations)

        # Need to add any configured relations
        configured_relations = self._relations.get_relations_for_epg(inheritance_policy.epg)
        for configured_relation in configured_relations:
            if configured_relation not in relations:
                logging.debug("Adding configured relation: %s", configured_relation)
                if not self._inheritance_tags.is_inherited(inheritance_policy.epg, configured_relation):
                    relations.append(configured_relation)
        return relations

    def _calculate_relations_for_app_policy(self, inheritance_policy):
        logging.debug("policy: %s", inheritance_policy)
        relations = []
        # Check if this policy is even inheritance enabled
        if not inheritance_policy.has_inherit_from():
            logging.warning("EPG is not inheriting from a parent EPG")
            return relations
        # Get the EPG that this policy is inheriting from
        parent_epg = inheritance_policy.inherit_from
        # Is inheritance allowed on that EPG ?
        if not self.cdb.is_inheritance_allowed(parent_epg):
            logging.warning("Parent EPG policy does not allow inheritance")
            return relations
        # Get the relations belonging to that EPG
        return self._relations.get_relations_for_epg(parent_epg)

    def calculate_relations(self):
        relations = {}
        for inheritance_policy in self.cdb.get_inheritance_policies():
            if not inheritance_policy.enabled:
                continue
            if inheritance_policy.epg.is_l3out():
                epg_relations = self._calculate_relations_for_l3out_policy(inheritance_policy)
            else:
                # TODO: may eventually need to process l2out
                epg_relations = self._calculate_relations_for_app_policy(inheritance_policy)
            relations[inheritance_policy.epg.get_json()] = epg_relations
        return relations

    def process_relation_event(self, event):
        logging.debug("process_event EVENT: %s", event.event)
        self._relations.store_relation(event)

    def process_subnet_event(self, event):
        logging.debug("Received subnet event: %s", event)
        # Store the subnet in the SubnetDB
        self._subnets.store_subnet_event(event)

    def process_inheritance_tag_event(self, event):
        logging.debug("Received subnet event: %s", event)
        # Store the tag in the TagDB
        self._inheritance_tags.store_tag(event)

    def _process_events(self, old_relations):
        while self.apic is None:
            pass

        if old_relations is None:
            old_relations = self._inheritance_tags.get_relations()

        # Check for any tag events
        for subscription in self._inheritance_tag_subscriptions:
            while self.apic.has_events(subscription):
                event = TagEvent(self.apic.get_event(subscription)["imdata"][0])
                self.process_inheritance_tag_event(event)

        # Check for relation events
        for subscription in self._relation_subscriptions:
            while self.apic.has_events(subscription):
                event = RelationEvent(self.apic.get_event(subscription)["imdata"][0])
                self.process_relation_event(event)

        # Check for subnet events
        for subscription in self._subnet_subscriptions:
            while self.apic.has_events(subscription):
                event = SubnetEvent(self.apic.get_event(subscription)["imdata"][0])
                self.process_subnet_event(event)

        # Calculate the new set of relations
        new_relations = self.calculate_relations()

        for old_epg in old_relations:
            logging.debug(old_epg)
        for new_epg in new_relations:
            logging.debug(new_epg)

        # Compare the old and the new relations for changes
        tenants = []
        for new_epg in new_relations:
            if new_epg not in old_relations:
                # New EPG, so we need to add all of the relations
                for new_relation in new_relations[new_epg]:
                    if self._inheritance_tags.is_inherited(EPGPolicy(json.loads(new_epg)), new_relation):
                        # If it's inherited, but we don't have the relation. We are likely coming up and not populated
                        # the old_relations yet
                        pass
                    # If just configured, we will have a relationDB entry. Otherwise, we need to inherit it
                    if self._relations.has_relation_for_epg(EPGPolicy(json.loads(new_epg)), new_relation):
                        continue
                    tenants = self.add_inherited_relation(tenants, EPGPolicy(json.loads(new_epg)), new_relation)
            else:
                # Handle any new added relations
                for new_relation in new_relations[new_epg]:
                    if new_relation not in old_relations[new_epg]:
                        if self._relations.has_relation_for_epg(EPGPolicy(json.loads(new_epg)), new_relation):
                            continue
                        tenants = self.add_inherited_relation(tenants, EPGPolicy(json.loads(new_epg)), new_relation)
                # Handle any deleted relations
                for old_relation in old_relations[new_epg]:
                    if old_relation not in new_relations[new_epg]:
                        if self._inheritance_tags.is_inherited(EPGPolicy(json.loads(new_epg)), old_relation):
                            tenants = self.remove_inherited_relation(
                                tenants, EPGPolicy(json.loads(new_epg)), old_relation
                            )
                        else:
                            # Must have been configured and manually deleted
                            pass
        for old_epg in old_relations:
            if old_epg not in new_relations:
                for old_relation in old_relations[old_epg]:
                    if self._inheritance_tags.is_inherited(EPGPolicy(json.loads(old_epg)), old_relation):
                        tenants = self.remove_inherited_relation(tenants, EPGPolicy(json.loads(old_epg)), old_relation)

        # Push the necessary config to the APIC
        for tenant in tenants:
            tenant_json = tenant.get_json()
            # Check that the tenant actually has the contracts since they may actually be tenant common contracts.
            # If tenant common is used, we need to clean up the tenant JSON to not create an empty contract within
            # this tenant
            for child in tenant_json["fvTenant"]["children"]:
                if "vzBrCP" in child:
                    if not self.does_tenant_have_contract(tenant.name, child["vzBrCP"]["attributes"]["name"]):
                        tenant_json["fvTenant"]["children"].remove(child)
                elif "vzCPIf" in child:
                    if not self.does_tenant_have_contract_if(tenant.name, child["vzCPIf"]["attributes"]["name"]):
                        tenant_json["fvTenant"]["children"].remove(child)
            logging.debug("Pushing tenant configuration to the APIC: %s", tenant_json)
            resp = self.apic.push_to_apic(tenant.get_url(), tenant_json)
            if resp.ok:
                logging.debug("Pushed to APIC successfully")
            else:
                logging.error("Error pushing to APIC", resp.text)
        return new_relations

    def run(self):
        loop_count = 0
        accelerated_cleanup_done = False
        while not self._exit:
            time.sleep(self._monitor_frequency)
            self._old_relations = self._process_events(self._old_relations)
Example #4
0
class Monitor(threading.Thread):
    """
    Thread responsible for monitoring EPG-to-Contract relations.
    """
    def __init__(self, cdb):
        threading.Thread.__init__(self)
        self.cdb = cdb
        self._monitor_frequency = 1
        self._exit = False
        self._relation_subscriptions = []
        self._inheritance_tag_subscriptions = []
        self._subnet_subscriptions = []
        self._relations = RelationDB()
        self._inheritance_tags = TagDB()
        self._subnets = SubnetDB()
        self.apic = None

    def exit(self):
        """
        Indicate that the thread should exit.
        """
        self._exit = True

    def subscribe(self, policy):
        query_url = ''
        epg = policy.epg
        epg_data = (epg.tenant, epg.epg_container_name, epg.name)
        if epg.epg_container_type == 'app':
            query_url = '/api/mo/uni/tn-%s/ap-%s/epg-%s.json' % epg_data
        elif epg.epg_container_type == 'l3out':
            subnet_query_url = '/api/mo/uni/tn-%s/out-%s/instP-%s.json' % epg_data
            subnet_query_url += '?query-target=subtree&target-subtree-class=l3extSubnet'
            subnet_query_url += '&subscription=yes'
            if subnet_query_url not in self._subnet_subscriptions:
                self.apic.subscribe(subnet_query_url)
                self._subnet_subscriptions.append(subnet_query_url)
            query_url = '/api/mo/uni/tn-%s/out-%s/instP-%s.json' % epg_data
        elif epg.epg_container_type == 'l2out':
            query_url = '/api/mo/uni/tn-%s/l2out-%s/instP-%s.json' % epg_data
        query_url += '?query-target=subtree&target-subtree-class=fvRsProv,fvRsCons,fvRsProtBy,fvRsConsIf'
        query_url += '&subscription=yes'
        if query_url not in self._relation_subscriptions:
            self.apic.subscribe(query_url)
            self._relation_subscriptions.append(query_url)

    def connect_to_apic(self):
        logging.debug('Connecting to APIC...')
        # Connect to APIC
        apic_config = self.cdb.get_apic_config()
        url = apic_config.ip_address
        if apic_config.use_https:
            url = 'https://' + url
        else:
            url = 'http://' + url
        if self.apic is not None:
            logging.debug('APIC is previously connected')
        self.apic = Session(url, apic_config.user_name, apic_config.password)
        resp = self.apic.login()

        # TODO: need to clear out the databases first

        # Get all of the subnets
        query_url = '/api/mo/uni.json?query-target=subtree&target-subtree-class=l3extSubnet'
        subnets = self.apic.get(query_url)
        for subnet in subnets.json()['imdata']:
            subnet_event = SubnetEvent(subnet)
            self._subnets.store_subnet_event(subnet_event)

        # Get all of the inherited relations
        tag_query_url = '/api/class/tagInst.json?query-target-filter=wcard(tagInst.name,"inherited:")'
        tags = self.apic.get(tag_query_url)
        for tag in tags.json()['imdata']:
            tag_event = TagEvent(tag)
            # Create a database entry for the inherited relations
            self._inheritance_tags.store_tag(tag_event)

        # Get all of the relations. We need this to track relations that are already present
        # i.e. configured but not through inheritance so that we can tell the difference
        query_url = '/api/mo/uni.json?query-target=subtree&target-subtree-class=fvRsProv,fvRsCons,fvRsProtBy,fvRsConsIf'
        relations = self.apic.get(query_url)
        for relation in relations.json()['imdata']:
            self._relations.store_relation(RelationEvent(relation))

        # Get all of the policies that are inherited from but not inheriting
        parent_only_policies = []
        for policy in self.cdb.get_inheritance_policies():
            inherited_from = False
            if policy.allowed and not policy.enabled:
                for child_policy in self.cdb.get_inheritance_policies():
                    if child_policy.has_inherit_from() and child_policy.inherit_from == policy.epg:
                        inherited_from = True
            if inherited_from:
                parent_only_policies.append(policy)

        # Issue all of the subscriptions
        for policy in self.cdb.get_inheritance_policies():
            self.subscribe(policy)
        for parent_only_policy in parent_only_policies:
            self.subscribe(parent_only_policy)
        tag_query_url += '&subscription=yes'
        self.apic.subscribe(tag_query_url)
        self._inheritance_tag_subscriptions.append(tag_query_url)

    def _add_inherited_relation(self, tenants, epg, relation, deleted=False):
        tenant_found = False

        # Find the tenant. Add if necessary
        for tenant in tenants:
            if tenant.name == epg.tenant:
                tenant_found = True
                break
        if not tenant_found:
            tenant = Tenant(epg.tenant)
            tenants.append(tenant)

        # Find the EPG Container. Add if necessary
        if epg.is_l3out:
            epg_container_class = OutsideL3
        else:
            epg_container_class = AppProfile
        epg_containers = tenant.get_children(only_class=epg_container_class)
        epg_container_found = False
        for epg_container in epg_containers:
            if epg_container.name == epg.epg_container_name:
                epg_container_found = True
                break
        if not epg_container_found:
            epg_container = epg_container_class(epg.epg_container_name, tenant)

        # Find the EPG. Add if necessary
        if epg.is_l3out:
            epg_class = OutsideEPG
        else:
            epg_class = EPG
        epgs = tenant.get_children(only_class=epg_class)
        epg_found = False
        for tenant_epg in epgs:
            if tenant_epg.name == epg.name:
                epg_found = True
                break
        if not epg_found:
            tenant_epg = epg_class(epg.name, epg_container)

        # Add the relation
        (relation_type, relation_name) = relation
        if relation_type == 'fvRsProv':
            contract = Contract(relation_name, tenant)
            tenant_epg.provide(contract)
            if deleted:
                tenant_epg.provide(contract)
                tenant_epg.dont_provide(contract)
        elif relation_type == 'fvRsCons':
            contract = Contract(relation_name, tenant)
            tenant_epg.consume(contract)
            if deleted:
                tenant_epg.consume(contract)
                tenant_epg.dont_consume(contract)
        elif relation_type == 'fvRsConsIf':
            contract_interface = ContractInterface(relation_name, tenant)
            tenant_epg.consume_cif(contract_interface)
            if deleted:
                tenant_epg.consume_cif(contract_interface)
                tenant_epg.dont_consume_cif(contract_interface)
        elif relation_type == 'fvRsProtBy':
            taboo = Taboo(relation_name, tenant)
            tenant_epg.protect(taboo)
            if deleted:
                tenant_epg.protect(taboo)
                tenant_epg.dont_protect(taboo)
        tenant_epg.add_tag('inherited:%s:%s' % (relation_type, relation_name))
        if deleted:
            tenant_epg.delete_tag('inherited:%s:%s' % (relation_type, relation_name))
        return tenants

    def add_inherited_relation(self, tenants, epg, relation):
        return self._add_inherited_relation(tenants, epg, relation)

    def remove_inherited_relation(self, tenants, epg, relation):
        return self._add_inherited_relation(tenants, epg, relation, deleted=True)

    def _calculate_relations_for_l3out_policy(self, inheritance_policy):
        logging.debug('_calculate_relations_for_l3out_policy: %s', inheritance_policy)
        # Get all of the EPGs covering this policy's EPG's subnets
        covering_epgs = self._subnets.get_all_covering_epgs(inheritance_policy.epg)

        # Remove any EPGs that are not allowed to be inherited
        for covering_epg in covering_epgs:
            if not self.cdb.is_inheritance_allowed(covering_epg):
                covering_epgs.remove(covering_epg)

        # Get all of the relations for the remaining covering EPGs
        relations = []
        for covering_epg in covering_epgs:
            epg_relations = self._relations.get_relations_for_epg(covering_epg)
            for epg_relation in epg_relations:
                if epg_relation not in relations:
                    relations.append(epg_relation)

        logging.debug('Relations to be inherited: %s', relations)

        # Need to add any configured relations
        configured_relations = self._relations.get_relations_for_epg(inheritance_policy.epg)
        for configured_relation in configured_relations:
            if configured_relation not in relations:
                logging.debug('Adding configured relation: %s', configured_relation)
                if not self._inheritance_tags.is_inherited(inheritance_policy.epg, configured_relation):
                    relations.append(configured_relation)
        return relations

    def _calculate_relations_for_app_policy(self, inheritance_policy):
        pass

    def calculate_relations(self):
        relations = {}
        for inheritance_policy in self.cdb.get_inheritance_policies():
            if not inheritance_policy.enabled:
                continue
            if inheritance_policy.epg.is_l3out():
                epg_relations = self._calculate_relations_for_l3out_policy(inheritance_policy)
            else:
                # TODO: may eventually need to process l2out
                self._calculate_relations_for_app_policy(inheritance_policy)
            relations[inheritance_policy.epg.get_json()] = epg_relations
        return relations

    def process_relation_event(self, event):
        logging.debug('process_event EVENT: %s', event.event)
        self._relations.store_relation(event)

    def process_subnet_event(self, event):
        logging.debug('Received subnet event: %s', event)
        # Store the subnet in the SubnetDB
        self._subnets.store_subnet_event(event)

    def process_inheritance_tag_event(self, event):
        logging.debug('Received subnet event: %s', event)
        # Store the tag in the TagDB
        self._inheritance_tags.store_tag(event)

    def _process_events(self, old_relations):
        while self.apic is None:
            pass

        # Check for any tag events
        for subscription in self._inheritance_tag_subscriptions:
            while self.apic.has_events(subscription):
                event = TagEvent(self.apic.get_event(subscription)['imdata'][0])
                self.process_inheritance_tag_event(event)

        # Check for relation events
        for subscription in self._relation_subscriptions:
            while self.apic.has_events(subscription):
                event = RelationEvent(self.apic.get_event(subscription)['imdata'][0])
                self.process_relation_event(event)

        # Check for subnet events
        for subscription in self._subnet_subscriptions:
            while self.apic.has_events(subscription):
                event = SubnetEvent(self.apic.get_event(subscription)['imdata'][0])
                self.process_subnet_event(event)

        # Calculate the new set of relations
        new_relations = self.calculate_relations()

        logging.debug('OLD_RELATIONS: %s %s', len(old_relations), old_relations)
        for old_epg in old_relations:
            logging.debug(old_epg)
        logging.debug('NEW_RELATIONS: %s %s', len(new_relations), new_relations)
        for new_epg in new_relations:
            logging.debug(new_epg)

        # Compare the old and the new relations for changes
        tenants = []
        for new_epg in new_relations:
            if new_epg not in old_relations:
                # New EPG, so we need to add all of the relations
                for new_relation in new_relations[new_epg]:
                    if self._inheritance_tags.is_inherited(EPGPolicy(json.loads(new_epg)), new_relation):
                        # If it's inherited, but we don't have the relation. We are likely coming up and not populated
                        # the old_relations yet
                        #continue
                        pass
                    # If just configured, we will have a relationDB entry. Otherwise, we need to inherit it
                    if self._relations.has_relation_for_epg(EPGPolicy(json.loads(new_epg)), new_relation):
                        continue
                    tenants = self.add_inherited_relation(tenants, EPGPolicy(json.loads(new_epg)), new_relation)
            else:
                # Handle any new added relations
                for new_relation in new_relations[new_epg]:
                    if new_relation not in old_relations[new_epg]:
                        if self._relations.has_relation_for_epg(EPGPolicy(json.loads(new_epg)), new_relation):
                            continue
                        tenants = self.add_inherited_relation(tenants, EPGPolicy(json.loads(new_epg)), new_relation)
                # Handle any deleted relations
                for old_relation in old_relations[new_epg]:
                    if old_relation not in new_relations[new_epg]:
                        if self._inheritance_tags.is_inherited(EPGPolicy(json.loads(new_epg)), old_relation):
                            tenants = self.remove_inherited_relation(tenants, EPGPolicy(json.loads(new_epg)), old_relation)
                        else:
                            # Must have been configured and manually deleted
                            pass
        for old_epg in old_relations:
            if old_epg not in new_relations:
                for old_relation in old_relations[old_epg]:
                    if self._inheritance_tags.is_inherited(EPGPolicy(json.loads(old_epg)), old_relation):
                        tenants = self.remove_inherited_relation(tenants, EPGPolicy(json.loads(old_epg)), old_relation)

        # Push the necessary config to the APIC
        for tenant in tenants:
            resp = tenant.push_to_apic(self.apic)
            if resp.ok:
                logging.debug('Pushed to APIC successfully')
            else:
                logging.error('Error pushing to APIC', resp.text)
        return new_relations

    def run(self):
        loop_count = 0
        accelerated_cleanup_done = False
        old_relations = {}
        while not self._exit:
            time.sleep(self._monitor_frequency)
            old_relations = self._process_events(old_relations)
Example #5
0
            urls.append(url)
        except:
            error("Error creating subscription for tenant '%s'" % tn.name)

    # Also, subscribe to the Tenant class so we can create new subscriptions
    # if new tenants get created.
    tn_url = "/api/class/fvTenant.json?subscription=yes"
    session.subscribe(tn_url, only_new=True)

    # Now we loop forever, waiting for any events
    output("Waiting for events...")
    while (True):

        # Check status of existing tenants
        for url in urls:
            if session.has_events(url):
                event_data = session.get_event(url)
                do_something(event_data)

        # Check for new tenants (if any tenants get added, we create a new
        # subscription; if they get deleted, we remove the existing one).
        if session.has_events(tn_url):
            tn = session.get_event(tn_url)
            tn_dn = tn['imdata'][0]['fvTenant']['attributes']['dn']
            url = "/api/mo/%s.json?query-target=subtree&subscription=yes" % tn_dn
            if tn['imdata'][0]['fvTenant']['attributes'][
                    'status'] == "created":
                debug("Subscribing to '%s'" % url)
                session.subscribe(url, only_new=True)
                urls.append(url)
            elif tn['imdata'][0]['fvTenant']['attributes'][