예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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
예제 #4
0
    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'])
예제 #5
0
    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'])
예제 #6
0
    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'])