示例#1
0
 def init(self):
     '''
     On initialization a mere sync request is sent and
     components are set to action.
     '''
     self.session.publish("ovs.sync_request", self.gre_endpoint[0])
     self.health_check = Task.TaskInterval(10, self.keepalive)
     self.health_check.start()
     self.etcd_client = EtcdClusterClient(self.etcd_addr[0], self.session.node_id, int(self.etcd_addr[1]))
 def init(self):
     '''
     On initialization a mere sync request is sent and
     components are set to action.
     '''
     self.interfaces = {}
     self.session.publish("ovs.sync_request", self.gre_endpoint[0])
     self.health_check = Task.TaskInterval(10, self.keepalive)
     self.health_check.start()
     self.etcd_client = EtcdClusterClient(self.etcd_addr[0],
                                          self.session.node_id,
                                          port=int(self.etcd_addr[1])
                                          )
     self.etcd_client.initStore()
     prev_networks = self.etcd_client.addNode()
     if prev_networks:
         for network in prev_networks:
             self.interfaces[network.id] = network
class ClusterState(object,metaclass=Singleton):
    '''
    an Cluster state is merely an object which
    implements few pub/sub methods. those methods
    are mainly for synchronizing the ovs helpers
    which are up in the moment and hold its properties
    every helper has a fork of these information to avoid
    SPoF.
    Once a helper is ready to join a cluster it makes a
    synchronization request. once other helpers receive
    that request they broadcast the helpers properties
    structure they hold. Thus, a request issuer initialize
    its structure and create gre tunnel to each helper
    Moreover a health check is made on an interval
    if a helper didn't respond for a few intervals
    it is deemed unhealthy and removed.
    an extra subscriber method is "hookContainer" which
    merely invokes pipework to hook a container received.

    Changes:
        Mainly all changes will occur here since our network
        compontents will be limited to endpoints, intefaces
        and containers which are manipulated by cygnet_common
        we won't need to manipulate them here, But refactor the
        code to a more generic code.
        We also might have to remove the data synchronization
        instead we can merely update and construct the layout
        over etcd as per components mentioned.

        values currently communicated via wamp are endpoints info
        and number in order to maintain properly network setup such as:
            tunnel names, remote ip addresses, container hooking
            directives..etc
        these values will be also communicated via etcd to construct a
        non-horizontal representation of the network.
    '''
    __metaclass__ = Singleton
    address1 = None
    interface = None
    etcd_addr = None

    def __init__(self, session):
        '''
        At initialization interface is supposed to be
        previously initialized and set as a class variable
        so we won't have to set containers or endpoints since
        they're held by the cygnetcommon interface.
        '''
        self.session = session
        self.gre_endpoint = self.address1
        self.gre_health = dict()

    def init(self):
        '''
        On initialization a mere sync request is sent and
        components are set to action.
        '''
        self.interfaces = {}
        self.session.publish("ovs.sync_request", self.gre_endpoint[0])
        self.health_check = Task.TaskInterval(10, self.keepalive)
        self.health_check.start()
        self.etcd_client = EtcdClusterClient(self.etcd_addr[0],
                                             self.session.node_id,
                                             port=int(self.etcd_addr[1])
                                             )
        self.etcd_client.initStore()
        prev_networks = self.etcd_client.addNode()
        if prev_networks:
            for network in prev_networks:
                self.interfaces[network.id] = network

    def keepalive(self):
        self.session.publish("ovs.sync_request", self.gre_endpoint[0])
        try:
            most = max([health for health in list(self.gre_health.values())])
        except ValueError as e:
            print(e)
            return
        gre_health_tmp = deepcopy(self.gre_health)
        for gre_endpoint, health in list(gre_health_tmp.items()):
            mask = [endpoint == gre_endpoint for endpoint in self.interface.endpoints]
            if (most - health) > 5 and sum(mask):
                # unhealthy endpoint -- remove
                idx = mask.index(True)
                self.interface.endpoints.pop(idx)
                self.gre_health.pop(gre_endpoint)
                # Broadcast modifications
                self.session.publish("ovs.sync_nodes", self.interface.endpoints)

    def addNetwork(self, network):
        network = self.interface.initContainerNetwork(network)
        if not network:
            raise RuntimeErorr("error: check if network is well configured")
        self.etcd_client.addNetwork(network)
        self.interfaces[network.id] = network

    def removeInterface(self, network_id):
        network = self.interfaces[network_id]
        removed = self.interface.destroyContainerNetwork(network)
        if not removed:
            raise RuntimeError("error: check if network is well configured")
        self.etcd_client.removeNetwork(network)

    # What should we sync?
    # 1- GRE endpoints
    @wamp.subscribe(u'ovs.sync_nodes')
    def syncNodes(self, gre_endpoints):
        print("Syncing: ", gre_endpoints)
        # Are we starting up?
        # a properly sat up cluster with more than two nodes will be refering
        # to the same number of endpoints
        # a one-node cluster should receive a full list of same up
        # endpoints from another
        if len(self.interface.endpoints) == 0 and len(gre_endpoints) != 0:
            self.interface.initContainerNetwork()
            for endpoint in gre_endpoints:
                self.interface.endpoints.append(endpoint)
        # Are we broadcasting new endpoint(s)
        elif len(self.interface.endpoints) < len(gre_endpoints):
            for gre_endpoint in gre_endpoints:
                if gre_endpoint not in self.interface.endpoints:
                    self.interface.endpoints.append(gre_endpoint)

        # Are we broadcasting endpoint leave?
        elif len(gre_endpoints) < len(self.interface.endpoints) and len(gre_endpoints) != 0:
            for gre_endpoint in self.interface.endpoints:
                if gre_endpoint not in gre_endpoints:
                    self.interface.endpoints.remove(gre_endpoint)

        # If we reach this point without updating GRE we're the second node up
        # We should act as the first GRE endpoint and re-broadcast
        # We don't have to update gre since Network Interface will do that for us
        # self.update_gre()
        print("Synced:", self.interface)
        print("ME:", self.gre_endpoint)

    @wamp.subscribe(u'ovs.sync_request')
    def syncRequest(self, origin):
        print("Request..")
        # we don't really care who issued
        if origin:
            if origin not in self.gre_health:
                self.gre_health[origin] = 0
            if len(self.interface.endpoints) == 0:
                self.interface.initContainerNetwork()
            if origin not in self.interface.endpoints:
                self.interface.endpoints.append(origin)
                to_sync = (self.interface.endpoints[:self.interface.endpoints.index(origin)] +
                           self.interface.endpoints[self.interface.endpoints.index(origin)+1:] +
                           [self.gre_endpoint[0]])
                self.session.publish("ovs.sync_nodes", to_sync)
            self.gre_health[origin] += 1
            self.gre_health[origin] = max([v for v in list(self.gre_health.values())])

    @wamp.subscribe(u'ovs.hook_container')
    def hookContainer(self, container):
        if not isinstance(container, Container):
            # WAMP received dict
             c = Container(container['Id'], container['Node'])
             c.address = container['Address']
             container = c

        if str(container.node) != self.session.node_id:
            return
        print(container)
        if container.address:
            self.interface.containers.append(container)
            return
        return

    @wamp.subscribe(u'ovs.unhook_container')
    def unhookContainer(self, container):
        if not isinstance(container, Container):
            # WAMP received dict
             c = Container(container['Id'], container['Node'])
             c.address = container['Address']
             container = c

        if str(container.node) != self.session.node_id:
            return
        if container.address:
            self.interface.containers.remove(container)