def get_mtu(): ''' Get user-specified MTU size, adjusted to make room for encapsulation headers. https://docs.projectcalico.org/networking/mtu ''' mtu = charm_config('veth-mtu') if not mtu: return None if charm_config('vxlan') != 'Never': return mtu - 50 elif charm_config('ipip') != 'Never': return mtu - 20 return mtu
def configure_bgp_globals(): status.maintenance('Configuring BGP globals') config = charm_config() try: try: bgp_config = calicoctl_get('bgpconfig', 'default') except CalledProcessError as e: if b'resource does not exist' in e.output: log('default BGPConfiguration does not exist') bgp_config = { 'apiVersion': 'projectcalico.org/v3', 'kind': 'BGPConfiguration', 'metadata': { 'name': 'default' }, 'spec': {} } else: raise spec = bgp_config['spec'] spec['asNumber'] = config['global-as-number'] spec['nodeToNodeMeshEnabled'] = config['node-to-node-mesh'] calicoctl_apply(bgp_config) except CalledProcessError: log(traceback.format_exc()) status.waiting('Waiting to retry BGP global configuration') return set_state('calico.bgp.globals.configured')
def install_calico_service(): ''' Install the calico-node systemd service. ''' status.maintenance('Installing calico-node service.') with open(kubernetes_common.kubeclientconfig_path) as f: kubeconfig = yaml.safe_load(f) any_file_changed([kubernetes_common.kubeclientconfig_path]) kubeconfig['users'] = [{ 'name': 'calico-node', 'user': { 'token': leader_get('calico-node-token') } }] kubeconfig['contexts'][0]['context']['user'] = '******' with open('/opt/calicoctl/kubeconfig', 'w') as f: yaml.dump(kubeconfig, f) etcd = endpoint_from_flag('etcd.available') service_path = os.path.join(os.sep, 'lib', 'systemd', 'system', 'calico-node.service') ip_versions = {net.version for net in get_networks(charm_config('cidr'))} ip4 = get_bind_address() if 4 in ip_versions else "none" ip6 = "autodetect" if 6 in ip_versions else "none" render( 'calico-node.service', service_path, { 'connection_string': etcd.get_connection_string(), 'etcd_key_path': ETCD_KEY_PATH, 'etcd_ca_path': ETCD_CA_PATH, 'etcd_cert_path': ETCD_CERT_PATH, 'nodename': gethostname(), # specify IP so calico doesn't grab a silly one from, say, lxdbr0 'ip': ip4, 'ip6': ip6, 'mtu': get_mtu(), 'calico_node_image': charm_config('calico-node-image'), 'ignore_loose_rpf': charm_config('ignore-loose-rpf'), 'lc_all': os.environ.get('LC_ALL', 'C.UTF-8'), 'lang': os.environ.get('LANG', 'C.UTF-8') }) check_call(['systemctl', 'daemon-reload']) service_restart('calico-node') service('enable', 'calico-node') remove_state('cni.kubeconfig.changed') set_state('calico.service.installed')
def is_rpf_config_mismatched(): with open('/proc/sys/net/ipv4/conf/all/rp_filter') as f: rp_filter = int(f.read()) ignore_loose_rpf = charm_config('ignore-loose-rpf') if rp_filter == 2 and not ignore_loose_rpf: # calico says this is invalid # https://github.com/kubernetes-sigs/kind/issues/891 return True return False
def configure_calico_pool(): ''' Configure Calico IP pool. ''' config = charm_config() if not config['manage-pools']: log('Skipping pool configuration') set_state('calico.pool.configured') return status.maintenance('Configuring Calico IP pool') try: # remove unrecognized pools, and default pool if CIDR doesn't match pools = calicoctl_get('pool')['items'] cidrs = tuple(cidr.strip() for cidr in config['cidr'].split(',')) names = tuple('ipv{}'.format(get_network(cidr).version) for cidr in cidrs) pool_names_to_delete = [ pool['metadata']['name'] for pool in pools if pool['metadata']['name'] not in names or pool['spec']['cidr'] not in cidrs ] for pool_name in pool_names_to_delete: log('Deleting pool: %s' % pool_name) calicoctl('delete', 'pool', pool_name, '--skip-not-exists') for cidr, name in zip(cidrs, names): # configure the default pool pool = { 'apiVersion': 'projectcalico.org/v3', 'kind': 'IPPool', 'metadata': { 'name': name, }, 'spec': { 'cidr': cidr, 'ipipMode': config['ipip'], 'vxlanMode': config['vxlan'], 'natOutgoing': config['nat-outgoing'], } } calicoctl_apply(pool) except CalledProcessError: log(traceback.format_exc()) if config['ipip'] != 'Never' and config['vxlan'] != 'Never': status.blocked('ipip and vxlan configs are in conflict') else: status.waiting('Waiting to retry calico pool configuration') return set_state('calico.pool.configured')
def configure_cni(): ''' Configure Calico CNI. ''' status.maintenance('Configuring Calico CNI') cni = endpoint_from_flag('cni.connected') etcd = endpoint_from_flag('etcd.available') os.makedirs('/etc/cni/net.d', exist_ok=True) ip_versions = {net.version for net in get_networks(charm_config('cidr'))} context = { 'connection_string': etcd.get_connection_string(), 'etcd_key_path': ETCD_KEY_PATH, 'etcd_cert_path': ETCD_CERT_PATH, 'etcd_ca_path': ETCD_CA_PATH, 'kubeconfig_path': '/opt/calicoctl/kubeconfig', 'mtu': get_mtu(), 'assign_ipv4': 'true' if 4 in ip_versions else 'false', 'assign_ipv6': 'true' if 6 in ip_versions else 'false', } render('10-calico.conflist', '/etc/cni/net.d/10-calico.conflist', context) config = charm_config() cni.set_config(cidr=config['cidr'], cni_conf_file='10-calico.conflist') set_state('calico.cni.configured')
def disable_vxlan_tx_checksumming(): '''Workaround for https://github.com/projectcalico/calico/issues/3145''' config = charm_config() if config['disable-vxlan-tx-checksumming'] and config['vxlan'] != 'Never': cmd = [ 'ethtool', '-K', 'vxlan.calico', 'tx-checksum-ip-generic', 'off' ] try: check_call(cmd) except CalledProcessError: msg = 'Waiting to retry disabling VXLAN TX checksumming' log(msg) status.waiting(msg)
def pull_calico_node_image(): image = resource_get('calico-node-image') if not image or os.path.getsize(image) == 0: status_set('maintenance', 'Pulling calico-node image') image = charm_config('calico-node-image') CTL.pull(image) else: status_set('maintenance', 'Loading calico-node image') unzipped = '/tmp/calico-node-image.tar' with gzip.open(image, 'rb') as f_in: with open(unzipped, 'wb') as f_out: f_out.write(f_in.read()) CTL.load(unzipped) set_state('calico.image.pulled')
def configure_calico_pool(): ''' Configure Calico IP pool. ''' config = charm_config() if not config['manage-pools']: log('Skipping pool configuration') set_state('calico.pool.configured') return status.maintenance('Configuring Calico IP pool') try: # remove unrecognized pools, and default pool if CIDR doesn't match pools = calicoctl_get('pool')['items'] pool_names_to_delete = [ pool['metadata']['name'] for pool in pools if pool['metadata']['name'] != 'default' or pool['spec']['cidr'] != config['cidr'] ] for pool_name in pool_names_to_delete: log('Deleting pool: %s' % pool_name) calicoctl('delete', 'pool', pool_name, '--skip-not-exists') # configure the default pool pool = { 'apiVersion': 'projectcalico.org/v3', 'kind': 'IPPool', 'metadata': { 'name': 'default' }, 'spec': { 'cidr': config['cidr'], 'ipipMode': config['ipip'], 'natOutgoing': config['nat-outgoing'] } } calicoctl_apply(pool) except CalledProcessError: log(traceback.format_exc()) status.waiting('Waiting to retry calico pool configuration') return set_state('calico.pool.configured')
def configure_cni(): ''' Configure Calico CNI. ''' status.maintenance('Configuring Calico CNI') cni = endpoint_from_flag('cni.is-worker') etcd = endpoint_from_flag('etcd.available') os.makedirs('/etc/cni/net.d', exist_ok=True) cni_config = cni.get_config() context = { 'connection_string': etcd.get_connection_string(), 'etcd_key_path': ETCD_KEY_PATH, 'etcd_cert_path': ETCD_CERT_PATH, 'etcd_ca_path': ETCD_CA_PATH, 'kubeconfig_path': cni_config['kubeconfig_path'] } render('10-calico.conflist', '/etc/cni/net.d/10-calico.conflist', context) config = charm_config() cni.set_config(cidr=config['cidr'], cni_conf_file='10-calico.conflist') set_state('calico.cni.configured')
def deploy_network_policy_controller(): ''' Deploy the Calico network policy controller. ''' status.maintenance('Deploying network policy controller.') etcd = endpoint_from_flag('etcd.available') context = { 'connection_string': etcd.get_connection_string(), 'etcd_key_path': ETCD_KEY_PATH, 'etcd_cert_path': ETCD_CERT_PATH, 'etcd_ca_path': ETCD_CA_PATH, 'calico_policy_image': charm_config('calico-policy-image'), 'etcd_cert_last_modified': os.path.getmtime(ETCD_CERT_PATH) } render('policy-controller.yaml', '/tmp/policy-controller.yaml', context) try: kubectl('apply', '-f', '/tmp/policy-controller.yaml') set_state('calico.npc.deployed') except CalledProcessError as e: status.waiting('Waiting for kubernetes') log(str(e))
def get_unit_as_number(): config = charm_config() # Check for matching unit rule unit_id = get_unit_id() unit_as_numbers = yaml.safe_load(config['unit-as-numbers']) if unit_id in unit_as_numbers: as_number = unit_as_numbers[unit_id] return as_number # Check for matching subnet rule subnet_as_numbers = yaml.safe_load(config['subnet-as-numbers']) subnets = filter_local_subnets(subnet_as_numbers) if subnets: subnets.sort(key=lambda subnet: -subnet.prefixlen) subnet = subnets[0] as_number = subnet_as_numbers[str(subnet)] return as_number # No AS number specified for this unit. return None
def install_calico_service(): ''' Install the calico-node systemd service. ''' status.maintenance('Installing calico-node service.') etcd = endpoint_from_flag('etcd.available') service_path = os.path.join(os.sep, 'lib', 'systemd', 'system', 'calico-node.service') render( 'calico-node.service', service_path, { 'connection_string': etcd.get_connection_string(), 'etcd_key_path': ETCD_KEY_PATH, 'etcd_ca_path': ETCD_CA_PATH, 'etcd_cert_path': ETCD_CERT_PATH, 'nodename': gethostname(), # specify IP so calico doesn't grab a silly one from, say, lxdbr0 'ip': get_bind_address(), 'calico_node_image': charm_config('calico-node-image') }) check_call(['systemctl', 'daemon-reload']) service_restart('calico-node') service('enable', 'calico-node') set_state('calico.service.installed')
def configure_master_cni(): status_set('maintenance', 'Configuring Calico CNI') cni = endpoint_from_flag('cni.is-master') config = charm_config() cni.set_config(cidr=config['cidr']) set_state('calico.cni.configured')
def get_route_reflector_cluster_id(): config = charm_config() route_reflector_cluster_ids = yaml.safe_load( config['route-reflector-cluster-ids']) unit_id = get_unit_id() return route_reflector_cluster_ids.get(unit_id)
def configure_bgp_peers(): status.maintenance('Configuring BGP peers') peers = [] # Global BGP peers config = charm_config() peers += yaml.safe_load(config['global-bgp-peers']) # Subnet-scoped BGP peers subnet_bgp_peers = yaml.safe_load(config['subnet-bgp-peers']) subnets = filter_local_subnets(subnet_bgp_peers) for subnet in subnets: peers += subnet_bgp_peers[str(subnet)] # Unit-scoped BGP peers unit_id = get_unit_id() unit_bgp_peers = yaml.safe_load(config['unit-bgp-peers']) if unit_id in unit_bgp_peers: peers += unit_bgp_peers[unit_id] # Give names to peers safe_unit_name = local_unit().replace('/', '-') named_peers = { # name must consist of lower case alphanumeric characters, '-' or '.' '%s-%s-%s' % (safe_unit_name, peer['address'].replace(':', '-'), peer['as-number']): peer for peer in peers } try: node_name = gethostname() for peer_name, peer in named_peers.items(): peer_def = { 'apiVersion': 'projectcalico.org/v3', 'kind': 'BGPPeer', 'metadata': { 'name': peer_name, }, 'spec': { 'node': node_name, 'peerIP': peer['address'], 'asNumber': peer['as-number'] } } calicoctl_apply(peer_def) # Delete unrecognized peers existing_peers = calicoctl_get('bgppeers')['items'] existing_peers = [peer['metadata']['name'] for peer in existing_peers] peers_to_delete = [ peer for peer in existing_peers if peer.startswith(safe_unit_name + '-') and peer not in named_peers ] for peer in peers_to_delete: calicoctl('delete', 'bgppeer', peer) except CalledProcessError: log(traceback.format_exc()) status.waiting('Waiting to retry BGP peer configuration') return set_state('calico.bgp.peers.configured')
def configure_master_cni(): status.maintenance('Configuring Calico CNI') cni = endpoint_from_flag('cni.is-master') config = charm_config() cni.set_config(cidr=config['cidr'], cni_conf_file='10-calico.conflist') set_state('calico.cni.configured')