def _get_subnet(self, subnet_id): # Read data for subnet SUBNET_ID from etcd and translate it to the dict # form expected for insertion into a Neutron NetModel. LOG.debug("Read subnet %s from etcd", subnet_id) self.dirty_subnets.discard(subnet_id) subnet_key = datamodel_v1.key_for_subnet(subnet_id) response = self.client.read(subnet_key, consistent=True) data = etcdutils.safe_decode_json(response.value, 'subnet') LOG.debug("Subnet data: %s", data) if not (isinstance(data, dict) and 'cidr' in data and 'gateway_ip' in data): # Subnet data was invalid. LOG.warning("Invalid subnet data: %s => %s", response.value, data) raise ValidationFailed("Invalid subnet data") # Convert to form expected by NetModel. ip_version = 6 if ':' in data['cidr'] else 4 subnet = {'enable_dhcp': True, 'ip_version': ip_version, 'cidr': data['cidr'], 'dns_nameservers': data.get('dns_servers') or [], 'id': subnet_id, 'gateway_ip': data['gateway_ip'], 'host_routes': data.get('host_routes', []), 'network_id': data.get('network_id', NETWORK_ID)} if ip_version == 6: subnet['ipv6_address_mode'] = DHCPV6_STATEFUL subnet['ipv6_ra_mode'] = DHCPV6_STATEFUL return dhcp.DictModel(subnet)
def _get_subnet(self, subnet_id): # Read data for subnet SUBNET_ID from etcd and translate it to the dict # form expected for insertion into a Neutron NetModel. LOG.debug("Read subnet %s from etcd", subnet_id) self.dirty_subnets.discard(subnet_id) subnet_key = datamodel_v1.key_for_subnet(subnet_id) response = self.client.read(subnet_key, consistent=True) data = etcdutils.safe_decode_json(response.value, 'subnet') LOG.debug("Subnet data: %s", data) if not (isinstance(data, dict) and 'cidr' in data and 'gateway_ip' in data): # Subnet data was invalid. LOG.warning("Invalid subnet data: %s => %s", response.value, data) raise ValidationFailed("Invalid subnet data") # Convert to form expected by NetModel. ip_version = 6 if ':' in data['cidr'] else 4 subnet = { 'enable_dhcp': True, 'ip_version': ip_version, 'cidr': data['cidr'], 'dns_nameservers': data.get('dns_servers') or [], 'id': subnet_id, 'gateway_ip': data['gateway_ip'], 'host_routes': data.get('host_routes', []), 'network_id': data.get('network_id', NETWORK_ID) } if ip_version == 6: subnet['ipv6_address_mode'] = DHCPV6_STATEFUL subnet['ipv6_ra_mode'] = DHCPV6_STATEFUL return dhcp.DictModel(subnet)
def on_subnet_set(self, response, subnet_id): """Handler for subnet creations and updates.""" LOG.debug("Subnet %s created or updated", subnet_id) subnet_data = etcdutils.safe_decode_json(response.value, 'subnet') if subnet_data is None: LOG.warning("Invalid subnet data %s", response.value) return if not (isinstance(subnet_data, dict) and 'cidr' in subnet_data and 'gateway_ip' in subnet_data): LOG.warning("Invalid subnet data: %s", subnet_data) return self.subnets_by_id[subnet_id] = subnet_data return
def on_endpoint_set(self, response, name): """Handler for endpoint creations and updates. Endpoint data is, for example: { 'interfaceName': port['interface_name'], 'mac': port['mac_address'], 'profiles': port['security_groups'], 'ipNetworks': ['10.28.0.2/32', '2001:db8:1::2/128'], 'ipv4Gateway': '10.28.0.1', 'ipv6Gateway': '2001:db8:1::1' } Port properties needed by DHCP code are: { 'id': <unique ID>, 'network_id': <network ID>, 'device_owner': 'calico', 'device_id': <Linux interface name>, 'fixed_ips': [ { 'subnet_id': <subnet ID>, 'ip_address': '10.28.0.2' } ], 'mac_address: <MAC address>, 'extra_dhcp_opts': ... (optional) } Network properties are: { 'subnets': [ <subnet object> ], 'id': <network ID>, 'namespace': None, 'ports: [ <port object> ], 'tenant_id': ? } Subnet properties are: { 'network_id': <network ID>, 'enable_dhcp': True, 'ip_version': 4 or 6, 'cidr': '10.28.0.0/24', 'dns_nameservers': [], 'id': <subnet ID>, 'gateway_ip': <gateway IP address>, 'host_routes': [], 'ipv6_address_mode': 'dhcpv6-stateful' | 'dhcpv6-stateless', 'ipv6_ra_mode': 'dhcpv6-stateful' | 'dhcpv6-stateless' } """ try: hostname, orchestrator, workload_id, endpoint_id = \ split_endpoint_name(name) except ValueError: # For some reason this endpoint's name does not have the expected # form. Ignore it. LOG.warning("Unexpected form for endpoint name: %s", name) return if hostname != self.hostname: LOG.info("Endpoint not on this node: %s", name) return # Get the endpoint spec. endpoint = etcdutils.safe_decode_json(response.value, 'endpoint') if not (isinstance(endpoint, dict) and 'spec' in endpoint and isinstance(endpoint['spec'], dict) and 'interfaceName' in endpoint['spec'] and 'ipNetworks' in endpoint['spec'] and 'mac' in endpoint['spec']): # Endpoint data is invalid; treat as deletion. LOG.warning("Invalid endpoint data: %s => %s", response.value, endpoint) self.on_endpoint_delete(None, name) return annotations = endpoint.get('metadata', {}).get('annotations', {}) endpoint = endpoint['spec'] # If the endpoint has no ipNetworks, treat as deletion. This happens # when a resync from the mechanism driver overlaps with a port/VM being # deleted. if not endpoint['ipNetworks']: LOG.info("Endpoint has no ipNetworks: %s", endpoint) self.on_endpoint_delete(None, name) return # Construct NetModel port equivalent of Calico's endpoint data. fixed_ips = [] dns_assignments = [] fqdn = annotations.get(datamodel_v3.ANN_KEY_FQDN) network_id = annotations.get(datamodel_v3.ANN_KEY_NETWORK_ID) allowedIps = [e.split('/')[0] for e in endpoint.get('allowedIps', [])] for addrm in endpoint['ipNetworks']: ip_addr = addrm.split('/')[0] if ip_addr in allowedIps: continue subnet_id = self.subnet_watcher.get_subnet_id_for_addr( ip_addr, network_id) or self.v1_subnet_watcher.get_subnet_id_for_addr( ip_addr, network_id) if subnet_id is None: LOG.warning("Missing subnet data for one of port's IPs") continue fixed_ips.append({'subnet_id': subnet_id, 'ip_address': ip_addr}) if fqdn: dns_assignments.append({ 'hostname': fqdn.split('.')[0], 'ip_address': ip_addr, 'fqdn': fqdn }) if not fixed_ips: LOG.warning("Endpoint has no DHCP-served IPs: %s", endpoint) return port = { 'id': endpoint_id, 'device_owner': 'calico', 'device_id': endpoint['interfaceName'], 'fixed_ips': fixed_ips, 'mac_address': endpoint['mac'], # FIXME: Calico currently does not pass through extra DHCP # options, but it would be nice if it did. Perhaps we could # use an endpoint annotation, similarly as we do for the FQDN. # https://bugs.launchpad.net/networking-calico/+bug/1553348 'extra_dhcp_opts': [] } if fqdn: port['dns_assignment'] = dns_assignments # Ensure that the cache includes the network and subnets for this port, # and set the port's network ID correctly. try: port['network_id'] = self._ensure_net_and_subnets(port) except SubnetIDNotFound: LOG.warning("Missing data for one of port's subnets") return # Report this at INFO level if it is a new port. Note, we # come through this code periodically for existing ports also, # because of how we watch the etcd DB for changes. if endpoint_id not in self.local_endpoint_ids: LOG.info("New port: %s", port) self.local_endpoint_ids.add(endpoint_id) else: LOG.debug("Refresh already known port: %s", port) # Add this port into the NetModel. self.agent.cache.put_port(dhcp.DictModel(port)) # Schedule updating Dnsmasq. self._update_dnsmasq(port['network_id'])
def on_endpoint_set(self, response, hostname, orchestrator, workload_id, endpoint_id): """Handler for endpoint creations and updates. Endpoint data is, for example: { 'state': 'active' or 'inactive', 'name': port['interface_name'], 'mac': port['mac_address'], 'profile_ids': port['security_groups'], 'ipv4_nets': ['10.28.0.2/32'], 'ipv4_gateway': '10.28.0.1', 'ipv6_nets': ['2001:db8:1::2/128'], 'ipv6_gateway': '2001:db8:1::1' } Port properties needed by DHCP code are: { 'id': <unique ID>, 'network_id': <network ID>, 'device_owner': 'calico', 'device_id': <Linux interface name>, 'fixed_ips': [ { 'subnet_id': <subnet ID>, 'ip_address': '10.28.0.2' } ], 'mac_address: <MAC address>, 'extra_dhcp_opts': ... (optional) } Network properties are: { 'subnets': [ <subnet object> ], 'id': <network ID>, 'namespace': None, 'ports: [ <port object> ], 'tenant_id': ? } Subnet properties are: { 'network_id': <network ID>, 'enable_dhcp': True, 'ip_version': 4 or 6, 'cidr': '10.28.0.0/24', 'dns_nameservers': [], 'id': <subnet ID>, 'gateway_ip': <gateway IP address>, 'host_routes': [], 'ipv6_address_mode': 'dhcpv6-stateful' | 'dhcpv6-stateless', 'ipv6_ra_mode': 'dhcpv6-stateful' | 'dhcpv6-stateless' } """ # Get the endpoint data. endpoint = etcdutils.safe_decode_json(response.value, 'endpoint') if not (isinstance(endpoint, dict) and 'ipv4_nets' in endpoint and 'ipv4_subnet_ids' in endpoint and 'ipv6_nets' in endpoint and 'ipv6_subnet_ids' in endpoint and 'name' in endpoint and 'mac' in endpoint): # Endpoint data is invalid. LOG.warning("Invalid endpoint data: %s => %s", response.value, endpoint) return # Construct NetModel port equivalent of Calico's endpoint data. fixed_ips = [] dns_assignments = [] fqdn = endpoint.get('fqdn') for ip_version in [4, 6]: # Generate the fixed IPs and DNS assignments for the current IP # version. for addrm, subnet_id in zip(endpoint['ipv%s_nets' % ip_version], endpoint['ipv%s_subnet_ids' % ip_version]): ip_addr = addrm.split('/')[0] fixed_ips.append({'subnet_id': subnet_id, 'ip_address': ip_addr}) if fqdn: dns_assignments.append({'hostname': fqdn.split('.')[0], 'ip_address': ip_addr, 'fqdn': fqdn}) port = {'id': endpoint_id, 'device_owner': 'calico', 'device_id': endpoint['name'], 'fixed_ips': fixed_ips, 'mac_address': endpoint['mac'], 'extra_dhcp_opts': []} if fqdn: port['dns_assignment'] = dns_assignments # Ensure that the cache includes the network and subnets for this port, # and set the port's network ID correctly. try: port['network_id'] = self._ensure_net_and_subnets(port) except etcd.EtcdKeyNotFound: LOG.warning("Missing data for one of port's subnets") return except ValidationFailed: LOG.warning("Invalid data for one of port's subnets") return # Add this port into the NetModel. LOG.debug("new port: %s", port) self.agent.cache.put_port(dhcp.DictModel(port)) # Schedule updating Dnsmasq. self._update_dnsmasq(port['network_id'])
def on_endpoint_set(self, response, hostname, orchestrator, workload_id, endpoint_id): """Handler for endpoint creations and updates. Endpoint data is, for example: { 'state': 'active' or 'inactive', 'name': port['interface_name'], 'mac': port['mac_address'], 'profile_ids': port['security_groups'], 'ipv4_nets': ['10.28.0.2/32'], 'ipv4_gateway': '10.28.0.1', 'ipv6_nets': ['2001:db8:1::2/128'], 'ipv6_gateway': '2001:db8:1::1' } Port properties needed by DHCP code are: { 'id': <unique ID>, 'network_id': <network ID>, 'device_owner': 'calico', 'device_id': <Linux interface name>, 'fixed_ips': [ { 'subnet_id': <subnet ID>, 'ip_address': '10.28.0.2' } ], 'mac_address: <MAC address>, 'extra_dhcp_opts': ... (optional) } Network properties are: { 'subnets': [ <subnet object> ], 'id': <network ID>, 'namespace': None, 'ports: [ <port object> ], 'tenant_id': ? } Subnet properties are: { 'network_id': <network ID>, 'enable_dhcp': True, 'ip_version': 4 or 6, 'cidr': '10.28.0.0/24', 'dns_nameservers': [], 'id': <subnet ID>, 'gateway_ip': <gateway IP address>, 'host_routes': [], 'ipv6_address_mode': 'dhcpv6-stateful' | 'dhcpv6-stateless', 'ipv6_ra_mode': 'dhcpv6-stateful' | 'dhcpv6-stateless' } """ # Get the endpoint data. endpoint = etcdutils.safe_decode_json(response.value, 'endpoint') if not (isinstance(endpoint, dict) and 'ipv4_nets' in endpoint and 'ipv4_subnet_ids' in endpoint and 'ipv6_nets' in endpoint and 'ipv6_subnet_ids' in endpoint and 'name' in endpoint and 'mac' in endpoint): # Endpoint data is invalid. LOG.warning("Invalid endpoint data: %s => %s", response.value, endpoint) return # Construct NetModel port equivalent of Calico's endpoint data. fixed_ips = [] dns_assignments = [] fqdn = endpoint.get('fqdn') for ip_version in [4, 6]: # Generate the fixed IPs and DNS assignments for the current IP # version. for addrm, subnet_id in zip( endpoint['ipv%s_nets' % ip_version], endpoint['ipv%s_subnet_ids' % ip_version]): ip_addr = addrm.split('/')[0] fixed_ips.append({ 'subnet_id': subnet_id, 'ip_address': ip_addr }) if fqdn: dns_assignments.append({ 'hostname': fqdn.split('.')[0], 'ip_address': ip_addr, 'fqdn': fqdn }) port = { 'id': endpoint_id, 'device_owner': 'calico', 'device_id': endpoint['name'], 'fixed_ips': fixed_ips, 'mac_address': endpoint['mac'], 'extra_dhcp_opts': [] } if fqdn: port['dns_assignment'] = dns_assignments # Ensure that the cache includes the network and subnets for this port, # and set the port's network ID correctly. try: port['network_id'] = self._ensure_net_and_subnets(port) except etcd.EtcdKeyNotFound: LOG.warning("Missing data for one of port's subnets") return except ValidationFailed: LOG.warning("Invalid data for one of port's subnets") return # Add this port into the NetModel. LOG.debug("new port: %s", port) self.agent.cache.put_port(dhcp.DictModel(port)) # Schedule updating Dnsmasq. self._update_dnsmasq(port['network_id'])