From 66cb349b9f772702b10cddbe6386fc82a01ce0c7 Mon Sep 17 00:00:00 2001 From: lucasew Date: Fri, 11 Aug 2023 13:06:32 -0300 Subject: [PATCH 1/2] [RFC 0159] General purpose allocator module Signed-off-by: lucasew --- rfcs/0159-general-purpose-allocator-module.md | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 rfcs/0159-general-purpose-allocator-module.md diff --git a/rfcs/0159-general-purpose-allocator-module.md b/rfcs/0159-general-purpose-allocator-module.md new file mode 100644 index 000000000..5887671f8 --- /dev/null +++ b/rfcs/0159-general-purpose-allocator-module.md @@ -0,0 +1,151 @@ +--- +feature: general-purpose-allocator-module +start-date: 2023-08-11 +author: lucasew +co-authors: (find a buddy later to help out with the RFC) +shepherd-team: (names, to be nominated and accepted by RFC steering committee) +shepherd-leader: (name to be appointed by RFC steering committee) +related-issues: (will contain links to implementation PRs) +--- + +# Summary +[summary]: #summary + +A function that generates a suggestion based item allocator module. + +# Motivation +[motivation]: #motivation + +Sometimes there are some values that the user don't actually care about which +value a option will get in some kind of space as long is a valid one and doesn't +conflict with other definitions. + +One of these spaces, for example, is the port space, like, non administrative ports +for servers (1025..49151). You will probably put a reverse proxy in front of it +so even the stability of the port number is not so important. + +# Detailed design +[design]: #detailed-design + +A function that receives the following parameters: +- `enableDescription`: The description of the enable option for one of the resources. Can also be a function that receives the value name and returns the description. +- `valueKey`: Key of the allocated value, like `value` or `port`. By default is `"value"`. +- `valueType`: Type of the value as the `type` parameter of `mkOption`. By default, as `mkOption`, is `null`. +- `valueApply`: Apply function passed to the value `mkOption`. By default, as `mkOption`, is `null`. +- `valueLiteral`: User friendly string representation of the value. By default is string-enclosed value passed to `keyFunc`. +- `valueDescription`: The description of the value option for one of the resources. Can also be a function that receives the value name and returns the description. +- `firstValue`: First item allocated. By default is `0`. +- `keyFunc`: Function that transforms the value to string in a way that uniquely identified the value for conflict checking. By default is `toString`. +- `succFunc`: Get the next value in the allocation space, like the next port or the next item of some item. This parameter is required. +- `validateFunc`: Function that returns if some value is valid. By default is the `valueType.check` function. +- `cfg`: As most of the module definitions in NixOS, receives the resolved reference of the option being defined. +- `keyPath`: Path in the module system to the option being defined. Used to give better error messages. +- `example`, `internal`, `relatedPackages`, `visible` and `description`: Just passed through to the outer `mkOption`. + +This function will return a NixOS module system module that will follow the following rough schema: + +``` + = { + = { + enable: boolean = false; + : = null; + }; +} +``` +The values checking will happen in the following order: + +- If any `..enable` is true and `..` is `null` it will suggest the next value available. + +- If any more than one `.` has the same ` ..` it will suggest that one of the values is changed to the suggested value. + +- If any `..enable` is true and ` ..` is `false` then it will list all the invalid value keys and suggest to change the first value key to the suggested value. + +Only one suggested value is generated per evaluation in one module, so it will give up on first fail. + +# Examples and Interactions +[examples-and-interactions]: #examples-and-interactions + +This is an example of a port allocator using the function, plus a usage example: + +```nix +{ config, lib }: +let + inherit (lib) types; + inherit (__future__) mkAllocModule; +in { + options.networking.ports = mkAllocModule { + valueKey = "port"; + valueType = types.port; + cfg = config.networking.ports; + description = "Build time port allocations for services that are only used internally"; + enableDescription = name: "Enable automatic port allocation for service ${name}"; + valueDescription = name: "Allocated port for service ${name}"; + + firstValue = 49151; + succFunc = x: x - 1; + valueLiteral = toString; + validateFunc = x: (types.port.check x) && (x > 1024); + keyPath = "networking.ports"; + example = literalExpression ''{ + app = { + enable = true; + port = 42069; # guided + }; + }''; + }; + + config.environment.etc = lib.pipe config.networking.ports [ + (attrNames) + (foldl' (x: y: x // { + "ports/${y}" = { + inherit (config.networking.ports.${y}) enable; + text = toString config.networking.ports.${y}.port; + }; + }) {}) + ]; + config.networking.ports = { + eoq = { + enable = false; + port = 22; + }; + trabson = { + enable = true; + port = 49139; + }; + }; +} +``` + +# Drawbacks +[drawbacks]: #drawbacks + +Evaluation time: the validation will need to happen everytime the module is used and the time it takes may be a problem. Allocators with many values can use a lot of recursion. IFD with an imperactive or a tail call optimized functional programming language for the validation phase may help. + +# Alternatives +[alternatives]: #alternatives + +Setting values by hand and hoping these values don't conflict on runtime. + +Just allocate items without logic for reserving values as suggested initially by [RFC 151](https://github.com/NixOS/rfcs/pull/151). + +# Unresolved questions +[unresolved]: #unresolved-questions + +Is this the right abstraction for a generic allocator? + +What about non primitive value allocations? + +What about maybe some kind of space that has 2D conflicts that would require two keys to keep track or some kind of nesting like subnets? + +Are function parameter names good enough? + +# Future work +[future]: #future-work + +Multiple machine based deployments. + +Network allocation for NixOS cluster guests. + +IPs for NixOS containers. + +Port allocation for local running services. From e7c342194dee9f35e12efa85f60520df58085f37 Mon Sep 17 00:00:00 2001 From: lucasew Date: Sun, 13 Aug 2023 13:36:55 -0300 Subject: [PATCH 2/2] rename mkAllocModule to mkAllocatorModule Signed-off-by: lucasew --- rfcs/0159-general-purpose-allocator-module.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/0159-general-purpose-allocator-module.md b/rfcs/0159-general-purpose-allocator-module.md index 5887671f8..00d0572e6 100644 --- a/rfcs/0159-general-purpose-allocator-module.md +++ b/rfcs/0159-general-purpose-allocator-module.md @@ -71,9 +71,9 @@ This is an example of a port allocator using the function, plus a usage example: { config, lib }: let inherit (lib) types; - inherit (__future__) mkAllocModule; + inherit (__future__) mkAllocatorModule; in { - options.networking.ports = mkAllocModule { + options.networking.ports = mkAllocatorModule { valueKey = "port"; valueType = types.port; cfg = config.networking.ports;