Example #1
0
class IpamPlugin(object):
    def __init__(self, environment, ipam_config):
        self.command = None
        """
        Command indicating which action to take - one of "ADD" or "DEL".
        """

        self.container_id = None
        """
        Identifier for the container for which we are performing IPAM.
        """

        self.datastore_client = IPAMClient()
        """
        Access to the datastore client.  Relies on ETCD_AUTHORITY environment
        variable being set by the calling plugin.
        """

        self.assign_ipv4 = ipam_config.get(ASSIGN_IPV4_KEY, "true") == "true"
        """
        Whether we should auto assign an IPv4 address - defaults to True.
        """

        self.assign_ipv6 = ipam_config.get(ASSIGN_IPV6_KEY, "false") == "true"
        """
        Whether we should auto assign an IPv6 address - defaults to False.
        """

        cni_args = parse_cni_args(environment.get(CNI_ARGS_ENV, ""))
        self.k8s_pod_name = cni_args.get(K8S_POD_NAME)
        self.k8s_namespace = cni_args.get(K8S_POD_NAMESPACE)
        """
        Only populated when running under Kubernetes.
        """

        """
        Only populated if the user requests a specific IP address.
        """
        self.ip = cni_args.get(CNI_ARGS_IP)

        # Validate the given environment and set fields.
        self._parse_environment(environment)

        if self.k8s_namespace and self.k8s_pod_name:
            self.workload_id = "%s.%s" % (self.k8s_namespace, self.k8s_pod_name)
        else:
            self.workload_id = self.container_id
        """
        Identifier for the workload.  In Kubernetes, this is the
        pod's namespace and name.  Otherwise, this is the container ID.
        """

    def execute(self):
        """
        Assigns or releases IP addresses for the specified workload.

        May raise CniError.

        :return: CNI ipam dictionary for ADD, None for DEL.
        """
        if self.command == "ADD":
            if self.ip:
                # The user has specifically requested an IP (v4) address.
                _log.info("User assigned address: %s for workload: %s",
                          self.ip,
                          self.workload_id)

                ipv4 = self._assign_existing_address()
                ipv6 = None
            else:
                # Auto-assign an IP address for this workload.
                _log.info("Assigning address to workload: %s", self.workload_id)
                ipv4, ipv6 = self._assign_address(handle_id=self.workload_id)

            # Build response dictionary.
            response = {}
            if ipv4:
                response["ip4"] = {"ip": str(ipv4.cidr)}
            if ipv6:
                response["ip6"] = {"ip": str(ipv6.cidr)}

            # Output the response and exit successfully.
            _log.debug("Returning response: %s", response)
            return json.dumps(response)
        else:
            # Release IPs using the workload_id as the handle.
            _log.info("Releasing addresses on workload: %s",
                      self.workload_id)
            try:
                self.datastore_client.release_ip_by_handle(
                        handle_id=self.workload_id
                )
            except KeyError:
                _log.warning("No IPs assigned to workload: %s",
                             self.workload_id)
                try:
                    # Try to release using the container ID.  Earlier
                    # versions of IPAM used the container ID alone
                    # as the handle. This allows us to be back-compatible.
                    _log.debug("Try release using container ID")
                    self.datastore_client.release_ip_by_handle(
                            handle_id=self.container_id
                    )
                except KeyError:
                    _log.debug("No IPs assigned to container: %s",
                               self.container_id)

    def _assign_address(self, handle_id):
        """
        Automatically assigns an IPv4 and an IPv6 address.

        :return: A tuple of (IPv4, IPv6) address assigned.
        """
        ipv4 = None
        ipv6 = None

        # Determine which addresses to assign.
        num_v4 = 1 if self.assign_ipv4 else 0
        num_v6 = 1 if self.assign_ipv6 else 0
        _log.info("Assigning %s IPv4 and %s IPv6 addresses", num_v4, num_v6)
        try:
            ipv4_addrs, ipv6_addrs = self.datastore_client.auto_assign_ips(
                num_v4=num_v4, num_v6=num_v6, handle_id=handle_id,
                attributes=None,
            )
            _log.debug("Allocated ip4s: %s, ip6s: %s", ipv4_addrs, ipv6_addrs)
        except RuntimeError as e:
            _log.error("Cannot auto assign IPAddress: %s", e.message)
            raise CniError(ERR_CODE_GENERIC,
                           msg="Failed to assign IP address",
                           details=e.message)
        else:
            if num_v4:
                try:
                    ipv4 = IPNetwork(ipv4_addrs[0])
                except IndexError:
                    _log.error("No IPv4 address returned, exiting")
                    raise CniError(ERR_CODE_GENERIC,
                                   msg="No IPv4 addresses available in pool")

            if num_v6:
                try:
                    ipv6 = IPNetwork(ipv6_addrs[0])
                except IndexError:
                    _log.error("No IPv6 address returned, exiting")
                    raise CniError(ERR_CODE_GENERIC,
                                   msg="No IPv6 addresses available in pool")

            _log.info("Assigned IPv4: %s, IPv6: %s", ipv4, ipv6)
            return ipv4, ipv6

    def _assign_existing_address(self):
        """
        Assign an address chosen by the user. IPv4 only.

        :return: The IPNetwork if successfully assigned.
        """
        try:
            address = IPAddress(self.ip, version=4)
        except AddrFormatError as e:
            _log.error("User requested IP: %s is invalid", self.ip)
            raise CniError(ERR_CODE_GENERIC,
                           msg="Failed to assign IP address",
                           details=e.message)
        try:
            self.datastore_client.assign_ip(address, self.workload_id, None)
        except AlreadyAssignedError as e:
            _log.error("User requested IP: %s is already assigned", self.ip)
            raise CniError(ERR_CODE_GENERIC,
                           msg="Failed to assign IP address",
                           details=e.message)
        except RuntimeError as e:
            _log.error("Cannot assign IPAddress: %s", e.message)
            raise CniError(ERR_CODE_GENERIC,
                           msg="Failed to assign IP address",
                           details=e.message)
        return IPNetwork(address)

    def _parse_environment(self, env):
        """
        Validates the plugins environment and extracts the required values.
        """
        _log.debug('Environment: %s', json.dumps(env, indent=2))

        # Check the given environment contains the required fields.
        try:
            self.command = env[CNI_COMMAND_ENV]
        except KeyError:
            raise CniError(ERR_CODE_GENERIC,
                           msg="Invalid arguments",
                           details="CNI_COMMAND not found in environment")
        else:
            # If the command is present, make sure it is valid.
            if self.command not in [CNI_CMD_ADD, CNI_CMD_DELETE]:
                raise CniError(ERR_CODE_GENERIC,
                               msg="Invalid arguments",
                               details="Invalid command '%s'" % self.command)
        try:
            self.container_id = env[CNI_CONTAINERID_ENV]
        except KeyError:
            raise CniError(ERR_CODE_GENERIC,
                           msg="Invalid arguments",
                           details="CNI_CONTAINERID not found in environment")
Example #2
0
class IpamPlugin(object):
    def __init__(self, environment, ipam_config):
        self.command = None
        """
        Command indicating which action to take - one of "ADD" or "DEL".
        """

        self.container_id = None
        """
        Identifier for the container for which we are performing IPAM.
        """

        self.datastore_client = IPAMClient()
        """
        Access to the datastore client.  Relies on ETCD_AUTHORITY environment
        variable being set by the calling plugin.
        """

        self.assign_ipv4 = ipam_config.get(ASSIGN_IPV4_KEY, "true") == "true"
        """
        Whether we should assign an IPv4 address - defaults to True.
        """

        self.assign_ipv6 = ipam_config.get(ASSIGN_IPV6_KEY, "false") == "true"
        """
        Whether we should assign an IPv6 address - defaults to False.
        """

        # Validate the given environment and set fields.
        self._parse_environment(environment)

    def execute(self):
        """
        Assigns or releases IP addresses for the specified container. 

        May raise CniError.
        
        :return: CNI ipam dictionary for ADD, None for DEL.
        """
        if self.command == "ADD":
            # Assign an IP address for this container.
            _log.info("Assigning address to container %s", self.container_id)
            ipv4, ipv6 = self._assign_address(handle_id=self.container_id)

            # Build response dictionary.
            response = {}
            if ipv4:
                response["ip4"] = {"ip": str(ipv4.cidr)}
            if ipv6:
                response["ip6"] = {"ip": str(ipv6.cidr)}
    
            # Output the response and exit successfully.
            _log.debug("Returning response: %s", response)
            return json.dumps(response)
        else:
            # Release IPs using the container_id as the handle.
            _log.info("Releasing addresses on container %s", 
                      self.container_id)
            try:
                self.datastore_client.release_ip_by_handle(handle_id=self.container_id)
            except KeyError:
                _log.warning("No IPs assigned to container_id %s", 
                             self.container_id)

    def _assign_address(self, handle_id):
        """
        Assigns an IPv4 and an IPv6 address. 
    
        :return: A tuple of (IPv4, IPv6) address assigned.
        """
        ipv4 = None 
        ipv6 = None 

        # Determine which addresses to assign.
        num_v4 = 1 if self.assign_ipv4 else 0
        num_v6 = 1 if self.assign_ipv6 else 0
        _log.info("Assigning %s IPv4 and %s IPv6 addresses", num_v4, num_v6)
        try:
            ipv4_addrs, ipv6_addrs = self.datastore_client.auto_assign_ips(
                num_v4=num_v4, num_v6=num_v6, handle_id=handle_id, 
                attributes=None,
            )
            _log.debug("Allocated ip4s: %s, ip6s: %s", ipv4_addrs, ipv6_addrs)
        except RuntimeError as e:
            _log.error("Cannot auto assign IPAddress: %s", e.message)
            raise CniError(ERR_CODE_GENERIC, 
                           msg="Failed to assign IP address",
                           details=e.message)
        else:
            if num_v4:
                try:
                    ipv4 = IPNetwork(ipv4_addrs[0])
                except IndexError:
                    _log.error("No IPv4 address returned, exiting")
                    raise CniError(ERR_CODE_GENERIC,
                                   msg="No IPv4 addresses available in pool")

            if num_v6:
                try:
                    ipv6 = IPNetwork(ipv6_addrs[0])
                except IndexError:
                    _log.error("No IPv6 address returned, exiting")
                    raise CniError(ERR_CODE_GENERIC,
                                   msg="No IPv6 addresses available in pool")

            _log.info("Assigned IPv4: %s, IPv6: %s", ipv4, ipv6)
            return ipv4, ipv6 

    def _parse_environment(self, env):
        """
        Validates the plugins environment and extracts the required values.
        """
        _log.debug('Environment: %s', json.dumps(env, indent=2))
    
        # Check the given environment contains the required fields.
        try:
            self.command = env[CNI_COMMAND_ENV]
        except KeyError:
            raise CniError(ERR_CODE_GENERIC,
                           msg="Invalid arguments",
                           details="CNI_COMMAND not found in environment")
        else:
            # If the command is present, make sure it is valid.
            if self.command not in [CNI_CMD_ADD, CNI_CMD_DELETE]:
                raise CniError(ERR_CODE_GENERIC,
                               msg="Invalid arguments",
                               details="Invalid command '%s'" % self.command)
        try:
            self.container_id = env[CNI_CONTAINERID_ENV]
        except KeyError:
            raise CniError(ERR_CODE_GENERIC,
                           msg="Invalid arguments",
                           details="CNI_CONTAINERID not found in environment")
Example #3
0
class IpamPlugin(object):
    def __init__(self, environment, ipam_config):
        self.command = None
        """
        Command indicating which action to take - one of "ADD" or "DEL".
        """

        self.container_id = None
        """
        Identifier for the container for which we are performing IPAM.
        """

        self.datastore_client = IPAMClient()
        """
        Access to the datastore client.  Relies on ETCD_AUTHORITY environment
        variable being set by the calling plugin.
        """

        self.assign_ipv4 = ipam_config.get(ASSIGN_IPV4_KEY, "true") == "true"
        """
        Whether we should auto assign an IPv4 address - defaults to True.
        """

        self.assign_ipv6 = ipam_config.get(ASSIGN_IPV6_KEY, "false") == "true"
        """
        Whether we should auto assign an IPv6 address - defaults to False.
        """

        cni_args = parse_cni_args(environment.get(CNI_ARGS_ENV, ""))
        self.k8s_pod_name = cni_args.get(K8S_POD_NAME)
        self.k8s_namespace = cni_args.get(K8S_POD_NAMESPACE)
        """
        Only populated when running under Kubernetes.
        """
        """
        Only populated if the user requests a specific IP address.
        """
        self.ip = cni_args.get(CNI_ARGS_IP)

        # Validate the given environment and set fields.
        self._parse_environment(environment)

        if self.k8s_namespace and self.k8s_pod_name:
            self.workload_id = "%s.%s" % (self.k8s_namespace,
                                          self.k8s_pod_name)
        else:
            self.workload_id = self.container_id
        """
        Identifier for the workload.  In Kubernetes, this is the
        pod's namespace and name.  Otherwise, this is the container ID.
        """

    def execute(self):
        """
        Assigns or releases IP addresses for the specified workload.

        May raise CniError.

        :return: CNI ipam dictionary for ADD, None for DEL.
        """
        if self.command == "ADD":
            if self.ip:
                # The user has specifically requested an IP (v4) address.
                _log.info("User assigned address: %s for workload: %s",
                          self.ip, self.workload_id)

                ipv4 = self._assign_existing_address()
                ipv6 = None
            else:
                # Auto-assign an IP address for this workload.
                _log.info("Assigning address to workload: %s",
                          self.workload_id)
                ipv4, ipv6 = self._assign_address(handle_id=self.workload_id)

            # Build response dictionary.
            response = {}
            if ipv4:
                response["ip4"] = {"ip": str(ipv4.cidr)}
            if ipv6:
                response["ip6"] = {"ip": str(ipv6.cidr)}

            # Output the response and exit successfully.
            _log.debug("Returning response: %s", response)
            return json.dumps(response)
        else:
            # Release IPs using the workload_id as the handle.
            _log.info("Releasing addresses on workload: %s", self.workload_id)
            try:
                self.datastore_client.release_ip_by_handle(
                    handle_id=self.workload_id)
            except KeyError:
                _log.warning("No IPs assigned to workload: %s",
                             self.workload_id)
                try:
                    # Try to release using the container ID.  Earlier
                    # versions of IPAM used the container ID alone
                    # as the handle. This allows us to be back-compatible.
                    _log.debug("Try release using container ID")
                    self.datastore_client.release_ip_by_handle(
                        handle_id=self.container_id)
                except KeyError:
                    _log.debug("No IPs assigned to container: %s",
                               self.container_id)

    def _assign_address(self, handle_id):
        """
        Automatically assigns an IPv4 and an IPv6 address.

        :return: A tuple of (IPv4, IPv6) address assigned.
        """
        ipv4 = None
        ipv6 = None

        # Determine which addresses to assign.
        num_v4 = 1 if self.assign_ipv4 else 0
        num_v6 = 1 if self.assign_ipv6 else 0
        _log.info("Assigning %s IPv4 and %s IPv6 addresses", num_v4, num_v6)
        try:
            ipv4_addrs, ipv6_addrs = self.datastore_client.auto_assign_ips(
                num_v4=num_v4,
                num_v6=num_v6,
                handle_id=handle_id,
                attributes=None,
            )
            _log.debug("Allocated ip4s: %s, ip6s: %s", ipv4_addrs, ipv6_addrs)
        except RuntimeError as e:
            _log.error("Cannot auto assign IPAddress: %s", e.message)
            raise CniError(ERR_CODE_GENERIC,
                           msg="Failed to assign IP address",
                           details=e.message)
        else:
            if num_v4:
                try:
                    ipv4 = IPNetwork(ipv4_addrs[0])
                except IndexError:
                    _log.error("No IPv4 address returned, exiting")
                    raise CniError(ERR_CODE_GENERIC,
                                   msg="No IPv4 addresses available in pool")

            if num_v6:
                try:
                    ipv6 = IPNetwork(ipv6_addrs[0])
                except IndexError:
                    _log.error("No IPv6 address returned, exiting")
                    raise CniError(ERR_CODE_GENERIC,
                                   msg="No IPv6 addresses available in pool")

            _log.info("Assigned IPv4: %s, IPv6: %s", ipv4, ipv6)
            return ipv4, ipv6

    def _assign_existing_address(self):
        """
        Assign an address chosen by the user. IPv4 only.

        :return: The IPNetwork if successfully assigned.
        """
        try:
            address = IPAddress(self.ip, version=4)
        except AddrFormatError as e:
            _log.error("User requested IP: %s is invalid", self.ip)
            raise CniError(ERR_CODE_GENERIC,
                           msg="Failed to assign IP address",
                           details=e.message)
        try:
            self.datastore_client.assign_ip(address, self.workload_id, None)
        except AlreadyAssignedError as e:
            _log.error("User requested IP: %s is already assigned", self.ip)
            raise CniError(ERR_CODE_GENERIC,
                           msg="Failed to assign IP address",
                           details=e.message)
        except RuntimeError as e:
            _log.error("Cannot assign IPAddress: %s", e.message)
            raise CniError(ERR_CODE_GENERIC,
                           msg="Failed to assign IP address",
                           details=e.message)
        return IPNetwork(address)

    def _parse_environment(self, env):
        """
        Validates the plugins environment and extracts the required values.
        """
        _log.debug('Environment: %s', json.dumps(env, indent=2))

        # Check the given environment contains the required fields.
        try:
            self.command = env[CNI_COMMAND_ENV]
        except KeyError:
            raise CniError(ERR_CODE_GENERIC,
                           msg="Invalid arguments",
                           details="CNI_COMMAND not found in environment")
        else:
            # If the command is present, make sure it is valid.
            if self.command not in [CNI_CMD_ADD, CNI_CMD_DELETE]:
                raise CniError(ERR_CODE_GENERIC,
                               msg="Invalid arguments",
                               details="Invalid command '%s'" % self.command)
        try:
            self.container_id = env[CNI_CONTAINERID_ENV]
        except KeyError:
            raise CniError(ERR_CODE_GENERIC,
                           msg="Invalid arguments",
                           details="CNI_CONTAINERID not found in environment")
Example #4
0
class IpamPlugin(object):
    def __init__(self, config, environment):
        self.config = config
        """
        Dictionary representation of the config passed via stdin.
        """

        self.env = environment
        """
        Current environment (e.g os.environ)
        """

        self.command = None
        """
        Command indicating which action to take - one of "ADD" or "DEL".
        """

        self.container_id = None
        """
        Identifier for the container for which we are performing IPAM.
        """

        self.datastore_client = IPAMClient()
        """
        Access to the datastore client.  Relies on ETCD_AUTHORITY environment
        variable being set by the calling plugin.
        """

        # Validate the given config and environment and set fields
        # using the given config and environment.
        self._parse_config()

    def execute(self):
        """
        Assigns or releases IP addresses for the specified container.
        :return:
        """
        if self.command == "ADD":
            # Assign an IP address for this container.
            _log.info("Assigning address to container %s", self.container_id)
            ipv4, ipv6 = self._assign_address(handle_id=self.container_id)
    
            # Output the response and exit successfully.
            print json.dumps({"ip4": {"ip": str(ipv4.cidr),},
                              "ip6": {"ip": str(ipv6.cidr),},})
        else:
            # Release any IP addresses for this container.
            assert self.command == CNI_CMD_DELETE, \
                    "Invalid command: %s" % self.command
    
            # Release IPs using the container_id as the handle.
            _log.info("Releasing addresses on container %s", 
                    self.container_id)
            try:
                self.datastore_client.release_ip_by_handle(handle_id=self.container_id)
            except KeyError:
                _log.warning("No IPs assigned to container_id %s", 
                        self.container_id)

    def _assign_address(self, handle_id, ipv4_pool=None, ipv6_pool=None):
        """
        Assigns an IPv4 and IPv6 address within the given pools.  
        If no pools are given, they will be automatically chosen.
    
        :return: A tuple of (IPv4, IPv6) address assigned.
        """
        ipv4 = IPNetwork("0.0.0.0") 
        ipv6 = IPNetwork("::") 
        pool = (ipv4_pool, ipv6_pool)
        try:
            ipv4_addrs, ipv6_addrs = self.datastore_client.auto_assign_ips(
                num_v4=1, num_v6=1, handle_id=handle_id, attributes=None,
                pool=pool
            )
            _log.debug("Allocated ip4s: %s, ip6s: %s", ipv4_addrs, ipv6_addrs)
        except RuntimeError as err:
            _log.error("Cannot auto assign IPAddress: %s", err.message)
            _exit_on_error(code=ERR_CODE_FAILED_ASSIGNMENT,
                           message="Failed to assign IP address",
                           details=err.message)
        else:
            try:
                ipv4 = ipv4_addrs[0]
            except IndexError:
                _log.error("No IPv4 address returned, exiting")
                _exit_on_error(code=ERR_CODE_FAILED_ASSIGNMENT,
                               message="No IPv4 addresses available in pool",
                               details = "")
            try:
                ipv6 = ipv6_addrs[0]
            except IndexError:
                _log.error("No IPv6 address returned, exiting")
                _exit_on_error(code=ERR_CODE_FAILED_ASSIGNMENT,
                               message="No IPv6 addresses available in pool",
                               details="")

            _log.info("Assigned IPv4: %s, IPv6: %s", ipv4, ipv6)
            return IPNetwork(ipv4), IPNetwork(ipv6)

    def _parse_config(self):
        """
        Validates that the plugins environment and given config contain 
        the required values.
        """
        _log.debug('Environment: %s', json.dumps(self.env, indent=2))
        _log.debug('Network config: %s', json.dumps(self.config, indent=2))
    
        # Check the given environment contains the required fields.
        try:
            self.command = self.env[CNI_COMMAND_ENV]
        except KeyError:
            _exit_on_error(code=ERR_CODE_INVALID_ARGUMENT,
                           message="Arguments Invalid",
                           details="CNI_COMMAND not found in environment")
        else:
            # If the command is present, make sure it is valid.
            if self.command not in [CNI_CMD_ADD, CNI_CMD_DELETE]:
                _exit_on_error(code=ERR_CODE_INVALID_ARGUMENT,
                               message="Arguments Invalid",
                               details="Invalid command '%s'" % self.command)

        try:
            self.container_id = self.env[CNI_CONTAINERID_ENV]
        except KeyError:
            _exit_on_error(code=ERR_CODE_INVALID_ARGUMENT,
                           message="Arguments Invalid",
                           details="CNI_CONTAINERID not found in environment")