def add_endpoint(self, event): endpoint_data = event.metadata service_name = endpoint_data['metadata']['name'] namespace = endpoint_data['metadata']['namespace'] ips = endpoint_data.get('custom', {}).get('ips', []) vlog.dbg("received endpoint data %s" % (endpoint_data)) cache_key = "%s_%s" % (namespace, service_name) cached_service = self.service_cache.get(cache_key, {}) if cached_service: service_data = cached_service else: try: response_json = kubernetes.get_service( variables.K8S_API_SERVER, namespace, service_name) except exceptions.NotFound: vlog.dbg("No service found for endpoint %s " % service_name) return except Exception as e: vlog.err("add_endpoint: k8s get service (%s)" % (str(e))) return service_data = response_json service_type = service_data['spec'].get('type') if service_type != "ClusterIP" and service_type != "NodePort": return self._update_vip(service_data, ips)
def _process_service_event(self, event): service_data = event["object"] vlog.dbg("obtained service data is %s" % json.dumps(service_data)) cluster_ip = service_data["spec"].get("clusterIP") # When service is created, we may get an event where there is no # cluster_ip (VIP) allocated to it. if not cluster_ip: return service_name = service_data["metadata"]["name"] namespace = service_data["metadata"]["namespace"] event_type = event["type"] cache_key = "%s_%s" % (namespace, service_name) cached_service = self.service_cache.get(cache_key, {}) self._update_service_cache(event_type, cache_key, service_data) # TODO: A service can be patched (i.e modified) and its ports # and external_ips can be changed. We need a way to handle it. # One way would be to send a delete and create connectivity events. has_conn_event = False if not cached_service: has_conn_event = True elif event_type == "DELETED": has_conn_event = True else: return if has_conn_event: vlog.dbg("Sending connectivity event for event %s on service %s" % (event_type, service_name)) self._send_connectivity_event(event_type, service_name, service_data)
def _process_pod_event(self, event): if event.event_type == "DELETED": vlog.dbg("Received a pod delete event %s" % (event.metadata)) self.mode.delete_logical_port(event) else: vlog.dbg("Received a pod ADD/MODIFY event %s" % (event.metadata)) self.mode.create_logical_port(event)
def accept(self): """Tries to accept a new connection on this passive stream. Returns (error, stream): if successful, 'error' is 0 and 'stream' is the new Stream object, and on failure 'error' is a positive errno value and 'stream' is None. Will not block waiting for a connection. If no connection is ready to be accepted, returns (errno.EAGAIN, None) immediately.""" if sys.platform == 'win32' and self.socket is None: return self.__accept_windows() while True: try: sock, addr = self.socket.accept() ovs.socket_util.set_nonblocking(sock) if (sys.platform != 'win32' and sock.family == socket.AF_UNIX): return 0, Stream(sock, "unix:%s" % addr, 0) return 0, Stream(sock, 'ptcp:%s:%s' % (addr[0], str(addr[1])), 0) except socket.error as e: error = ovs.socket_util.get_exception_errno(e) if sys.platform == 'win32' and error == errno.WSAEWOULDBLOCK: # WSAEWOULDBLOCK would be the equivalent on Windows # for EAGAIN on Unix. error = errno.EAGAIN if error != errno.EAGAIN: # XXX rate-limit vlog.dbg("accept: %s" % os.strerror(error)) return error, None
def _process_pod_event(self, event): vlog.dbg("obtained pod event %s" % json.dumps(event)) pod_data = event['object'] event_type = event['type'] pod_name = pod_data['metadata'].get('name') namespace = pod_data['metadata'].get('namespace') if not pod_name or not namespace: return # To create a logical port for a pod, we need to know the node # where it has been scheduled. The first event from the API server # may not have this information, but we will eventually get it. if event_type != 'DELETED' and not pod_data['spec'].get('nodeName'): return cache_key = "%s_%s" % (namespace, pod_name) cached_pod = self.pod_cache.get(cache_key, {}) self._update_pod_cache(event_type, cache_key, pod_data) has_conn_event = False if not cached_pod: has_conn_event = True elif event_type == 'DELETED': has_conn_event = True else: return if has_conn_event: vlog.dbg("Sending connectivity event for event %s on pod %s" % (event_type, pod_name)) self._send_connectivity_event(event_type, pod_name, pod_data)
def _process_service_event(self, event): if event.event_type == "DELETED": vlog.dbg("Received a service delete event %s" % (event.metadata)) else: vlog.dbg("Received a service ADD/MODIFY event %s" % (event.metadata)) self.mode.update_vip(event)
def __call__(self, request): handler = self.get_handler(request) if handler: vlog.dbg("Handling request '%s %s' with %s" % (request.method, request.path, str(handler))) return handler.handle_request(request) else: return NOT_SUPPORTED_RESPONSE
def _create_load_balancer_vip(self, namespace, load_balancer, service_ip, ips, port, target_port, protocol): # With service_ip:port as a VIP, create an entry in 'load_balancer' vlog.dbg("received event to create/modify load_balancer (%s) vip " "service_ip=%s, ips=%s, port=%s, target_port=%s, protocol=%s" % (load_balancer, service_ip, ips, port, target_port, protocol)) if not port or not target_port or not protocol or not load_balancer: return # key is of the form "IP:port" (with quotes around) key = "\"" + service_ip + ":" + str(port) + "\"" if not ips: try: ovn_nbctl("remove", "load_balancer", load_balancer, "vips", key) except Exception as e: vlog.err("_create_load_balancer_vip remove: (%s)" % (str(e))) return if target_port.isdigit(): # target is of the form "IP1:port, IP2:port, IP3:port" target_endpoints = ",".join(["%s:%s" % (ip, target_port) for ip in ips]) else: # 'target_port' is a string. We should get its number # from the cache. if not self.port_name_cache.get(namespace): vlog.warn("targetPort of %s in ns %s does not have an " "associated port. Ignoring endpoint creation." % (target_port, namespace)) return target_endpoint_list = [] for ip in ips: if not self.port_name_cache[namespace].get(ip): continue num_port = self.port_name_cache[namespace][ip].get(target_port) if not num_port: continue target_endpoint_list.append("%s:%s" % (ip, num_port)) if not target_endpoint_list: vlog.warn("targetPort of %s in ns %s does not have any " "associated ports. Ignoring endpoint creation." % (target_port, namespace)) return target_endpoints = ",".join(target_endpoint_list) target = "\"" + target_endpoints + "\"" try: ovn_nbctl("set", "load_balancer", load_balancer, "vips:" + key + "=" + target) except Exception as e: vlog.err("_create_load_balancer_vip add: (%s)" % (str(e)))
def _transition(self, now, state): if self.state == Reconnect.ConnectInProgress: self.n_attempted_connections += 1 if state == Reconnect.Active: self.n_successful_connections += 1 connected_before = self.state.is_connected connected_now = state.is_connected if connected_before != connected_now: if connected_before: self.total_connected_duration += now - self.last_connected self.seqno += 1 vlog.dbg("%s: entering %s" % (self.name, state.name)) self.state = state self.state_entered = now
def run(self): events = [] while True: try: # TODO: Not sure how wait with timeout plays with eventlet event = self.event_queue.get_nowait() events.append(event) vlog.dbg("Received event %s from %s" % (event.event_type, event.source)) except queue.Empty: # no element in the queue if events: self.process_events(events) events = [] else: # TODO: Do something better here. time.sleep(0.1)
def __log_wakeup(self, events): if not events: vlog.dbg("%d-ms timeout" % self.timeout) else: for fd, revents in events: if revents != 0: s = "" if revents & POLLIN: s += "[POLLIN]" if revents & POLLOUT: s += "[POLLOUT]" if revents & POLLERR: s += "[POLLERR]" if revents & POLLHUP: s += "[POLLHUP]" if revents & POLLNVAL: s += "[POLLNVAL]" vlog.dbg("%s on fd %d" % (s, fd))
def _create_load_balancer_vip(self, service_type, service_ip, ips, port, target_port, protocol): vlog.dbg("received event to create/modify load_balancer vip with " "service_type=%s, service_ip=%s, ips=%s, port=%s," "target_port=%s, protocol=%s" % (service_type, service_ip, ips, port, target_port, protocol)) if not port or not target_port or not protocol or not service_type: return load_balancer = "" if protocol == "TCP" and service_type == "ClusterIP": load_balancer = variables.K8S_CLUSTER_LB_TCP elif protocol == "UDP" and service_type == "ClusterIP": load_balancer = variables.K8S_CLUSTER_LB_UDP elif protocol == "TCP" and service_type == "NodePort": load_balancer = variables.K8S_NS_LB_TCP elif protocol == "UDP" and service_type == "NodePort": load_balancer = variables.K8S_NS_LB_UDP if not load_balancer: return # key is of the form "IP:port" (with quotes around) key = "\"" + service_ip + ":" + str(port) + "\"" if not ips: try: ovn_nbctl("remove", "load_balancer", load_balancer, "vips", key) except Exception as e: vlog.err("_create_load_balancer_vip remove: (%s)" % (str(e))) return # target is of the form "IP1:port, IP2:port, IP3:port" target_endpoints = ",".join(["%s:%s" % (ip, target_port) for ip in ips]) target = "\"" + target_endpoints + "\"" try: ovn_nbctl("set", "load_balancer", load_balancer, "vips:" + key + "=" + target) except Exception as e: vlog.err("_create_load_balancer_vip add: (%s)" % (str(e)))
def accept(self): """Tries to accept a new connection on this passive stream. Returns (error, stream): if successful, 'error' is 0 and 'stream' is the new Stream object, and on failure 'error' is a positive errno value and 'stream' is None. Will not block waiting for a connection. If no connection is ready to be accepted, returns (errno.EAGAIN, None) immediately.""" while True: try: sock, addr = self.socket.accept() ovs.socket_util.set_nonblocking(sock) return 0, Stream(sock, "unix:%s" % addr, 0) except socket.error as e: error = ovs.socket_util.get_exception_errno(e) if error != errno.EAGAIN: # XXX rate-limit vlog.dbg("accept: %s" % os.strerror(error)) return error, None
def get_pod_annotations(server, namespace, pod): ca_certificate, api_token = _get_api_params() url = ("%s/api/v1/namespaces/%s/pods/%s" % (server, namespace, pod)) headers = {} if api_token: headers['Authorization'] = 'Bearer %s' % api_token if ca_certificate: response = requests.get(url, headers=headers, verify=ca_certificate) else: response = requests.get(url, headers=headers) if not response: # TODO: raise here return json_response = response.json() annotations = json_response['metadata'].get('annotations') vlog.dbg("Annotations for pod %s: %s" % (pod, annotations)) return annotations
def update_vip(self, event): service_data = event.metadata service_type = service_data['spec'].get('type') vlog.dbg("update_vip: received service data %s" % (service_data)) # We only care about services that are of type 'clusterIP' and # 'nodePort'. if service_type != "ClusterIP" and service_type != "NodePort": return service_name = service_data['metadata']['name'] event_type = event.event_type namespace = service_data['metadata']['namespace'] cache_key = "%s_%s" % (namespace, service_name) self._update_service_cache(event_type, cache_key, service_data) if event.event_type == "DELETED": vlog.dbg("received service delete event.") self._update_vip(service_data, None)
def _process_endpoint_event(self, event): vlog.dbg("obtained endpoint event %s" % json.dumps(event)) endpoint_data = event['object'] event_type = event['type'] endpoint_id = endpoint_data['metadata']['uid'] endpoint_name = endpoint_data['metadata'].get('name') namespace = endpoint_data['metadata'].get('namespace') ips = set() subsets = endpoint_data.get('subsets') if subsets: for subset in subsets: addresses = subset.get('addresses') if not addresses: continue for address in addresses: ip = address.get('ip') if ip: ips.add(ip) if not endpoint_name or not namespace: return cached_endpoint = self.endpoint_cache.get(endpoint_id) if (not cached_endpoint or cached_endpoint.get('custom', {}).get('ips') != ips): vlog.dbg("Sending connectivity event for event %s on endpoint %s" % (event_type, endpoint_name)) if cached_endpoint: custom_data = cached_endpoint.setdefault('custom', {}) else: custom_data = {} custom_data['ips'] = ips endpoint_data['custom'] = custom_data self._send_connectivity_event(event_type, endpoint_name, endpoint_data) # Update cache self.endpoint_cache[endpoint_id] = endpoint_data
def set_pod_annotation(server, namespace, pod, key, value): ca_certificate, api_token = _get_api_params() url = ("%s/api/v1/namespaces/%s/pods/%s" % (server, namespace, pod)) # NOTE: This is not probably compliant with RFC 7386 but appears to work # with the kubernetes API server. patch = { 'metadata': { 'annotations': { key: value } } } headers = {'Content-Type': 'application/merge-patch+json'} if api_token: headers['Authorization'] = 'Bearer %s' % api_token if ca_certificate: response = requests.patch( url, data=json.dumps(patch), headers=headers, verify=ca_certificate) else: response = requests.patch( url, data=json.dumps(patch), headers=headers) if not response: # TODO: Raise appropriate exception raise Exception("Something went wrong while annotating pod: %s" % response.text) json_response = response.json() annotations = json_response['metadata'].get('annotations') vlog.dbg("Annotations for pod after update %s: %s" % (pod, annotations)) return annotations
def __log_msg(self, title, msg): if vlog.dbg_is_enabled(): vlog.dbg("%s: %s %s" % (self.name, title, msg))
def __read_pidfile(pidfile, delete_if_stale): if _pidfile_dev is not None: try: s = os.stat(pidfile) if s.st_ino == _pidfile_ino and s.st_dev == _pidfile_dev: # It's our own pidfile. We can't afford to open it, # because closing *any* fd for a file that a process # has locked also releases all the locks on that file. # # Fortunately, we know the associated pid anyhow. return os.getpid() except OSError: pass try: file_handle = open(pidfile, "r+") except IOError as e: if e.errno == errno.ENOENT and delete_if_stale: return 0 vlog.warn("%s: open: %s" % (pidfile, e.strerror)) return -e.errno # Python fcntl doesn't directly support F_GETLK so we have to just try # to lock it. try: fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB) # pidfile exists but wasn't locked by anyone. Now we have the lock. if not delete_if_stale: file_handle.close() vlog.warn("%s: pid file is stale" % pidfile) return -errno.ESRCH # Is the file we have locked still named 'pidfile'? try: raced = False s = os.stat(pidfile) s2 = os.fstat(file_handle.fileno()) if s.st_ino != s2.st_ino or s.st_dev != s2.st_dev: raced = True except IOError: raced = True if raced: vlog.warn("%s: lost race to delete pidfile" % pidfile) return -errno.EALREADY # We won the right to delete the stale pidfile. try: os.unlink(pidfile) except IOError as e: vlog.warn("%s: failed to delete stale pidfile (%s)" % (pidfile, e.strerror)) return -e.errno else: vlog.dbg("%s: deleted stale pidfile" % pidfile) file_handle.close() return 0 except IOError as e: if e.errno not in [errno.EACCES, errno.EAGAIN]: vlog.warn("%s: fcntl: %s" % (pidfile, e.strerror)) return -e.errno # Someone else has the pidfile locked. try: try: error = int(file_handle.readline()) except IOError as e: vlog.warn("%s: read: %s" % (pidfile, e.strerror)) error = -e.errno except ValueError: vlog.warn("%s does not contain a pid" % pidfile) error = -errno.EINVAL return error finally: try: file_handle.close() except IOError: pass
raced = True except IOError: raced = True if raced: vlog.warn("%s: lost race to delete pidfile" % pidfile) return -errno.EALREADY # We won the right to delete the stale pidfile. try: os.unlink(pidfile) except IOError, e: vlog.warn("%s: failed to delete stale pidfile (%s)" % (pidfile, e.strerror)) return -e.errno else: vlog.dbg("%s: deleted stale pidfile" % pidfile) file_handle.close() return 0 except IOError, e: if e.errno not in [errno.EACCES, errno.EAGAIN]: vlog.warn("%s: fcntl: %s" % (pidfile, e.strerror)) return -e.errno # Someone else has the pidfile locked. try: try: error = int(file_handle.readline()) except IOError, e: vlog.warn("%s: read: %s" % (pidfile, e.strerror)) error = -e.errno except ValueError:
def __log_msg(self, title, msg): vlog.dbg("%s: %s %s" % (self.name, title, msg))
def write(data, extschema, idl, txn=None, block=False): """Write a new configuration to OpenSwitch OVSDB database Args: data (dict): The new configuration represented as a Python dictionary object. extschema (opslib.RestSchema): This is the parsed extended-schema (vswitch.extschema) object. idl (ovs.db.idl.Idl): This is the IDL object that represents the OVSDB IDL. txn (ovs.db.idl.Transaction): OVSDB transaction object. block (boolean): if block is True, commit_block() is used Returns: result : The result of transaction commit """ if txn is None: try: txn = Transaction(idl) block = True except AssertionError as e: vlog.dbg('error in creating transaction: %s' % e) return e # dc.read returns config db with 'System' table # indexed to 'System' keyword. Replace it with # current database's System row UUID so that all # tables in 'data' are represented the same way try: system_uuid = idl.tables[ ops.constants.OVSDB_SCHEMA_SYSTEM_TABLE].rows.keys()[0] data[ops.constants.OVSDB_SCHEMA_SYSTEM_TABLE] = { system_uuid: data[ops.constants.OVSDB_SCHEMA_SYSTEM_TABLE] } _write.setup_validators(extschema, idl) # iterate over all top-level tables i.e. root for table_name, tableschema in extschema.ovs_tables.iteritems(): # iterate over non-children tables if extschema.ovs_tables[table_name].parent is not None: continue # set up the non-child table _write.setup_table(table_name, data, extschema, idl, txn) # iterate over all tables to fill in references for table_name, tableschema in extschema.ovs_tables.iteritems(): if extschema.ovs_tables[table_name].parent is not None: continue _write.setup_references(table_name, data, extschema, idl) validation_errors = _write.exec_validators() if validation_errors: return (txn.ERROR, validation_errors) except Exception as e: txn.abort() return (txn.ERROR, e) try: if not block: # txn maybe be incomplete result = txn.commit() else: # txn will block until it is completed result = txn.commit_block() vlog.dbg('transacton result: %s' % result) return (result, txn.get_error()) except Exception as e: vlog.err('transaction exception: %s' % e) txn.abort() return (txn.ERROR, e)
def run(fsm, now): vlog.dbg("%s: idle %d ms, sending inactivity probe" % (fsm.name, now - max(fsm.last_activity, fsm.state_entered))) fsm._transition(now, Reconnect.Idle) return PROBE
def run(fsm, now): vlog.dbg("%s: idle %d ms, sending inactivity probe" % (fsm.name, now - max(fsm.last_received, fsm.state_entered))) fsm._transition(now, Reconnect.Idle) return PROBE
and msg.method == "stolen"): # Someone else stole our lock. self.__parse_lock_notify(msg.params, False) elif msg.type == ovs.jsonrpc.Message.T_NOTIFY and msg.id == "echo": # Reply to our echo request. Ignore it. pass elif (msg.type in (ovs.jsonrpc.Message.T_ERROR, ovs.jsonrpc.Message.T_REPLY) and self.__txn_process_reply(msg)): # __txn_process_reply() did everything needed. pass else: # This can happen if a transaction is destroyed before we # receive the reply, so keep the log level low. vlog.dbg("%s: received unexpected %s message" % (self._session.get_name(), ovs.jsonrpc.Message.type_to_string(msg.type))) return initial_change_seqno != self.change_seqno def wait(self, poller): """Arranges for poller.block() to wake up when self.run() has something to do or when activity occurs on a transaction on 'self'.""" self._session.wait(poller) self._session.recv_wait(poller) def has_ever_connected(self): """Returns True, if the IDL successfully connected to the remote database and retrieved its contents (even if the connection subsequently dropped and is in the process of reconnecting). If so, then the IDL contains an atomic snapshot of the database's contents
def _process_endpoint_event(self, event): if event.event_type != "DELETED": vlog.dbg("Received a endpoint ADD/MODIFY event %s" % (event.metadata)) self.mode.add_endpoint(event)