Exemple #1
0
 def read(self, request, id):
     """Read a specific notification."""
     notification = get_object_or_404(Notification, id=id)
     if notification.is_relevant_to(request.user):
         return notification
     elif request.user.is_superuser:
         return notification
     else:
         raise MAASAPIForbidden()
Exemple #2
0
    def dismiss(self, request, id):
        """Dismiss a specific notification.

        Returns HTTP 403 FORBIDDEN if this notification is not relevant
        (targeted) to the invoking user.

        It is safe to call multiple times for the same notification.
        """
        notification = get_object_or_404(Notification, id=id)
        if notification.is_relevant_to(request.user):
            notification.dismiss(request.user)
        else:
            raise MAASAPIForbidden()
Exemple #3
0
    def create_physical(self, request, system_id):
        """Create a physical interface on a machine and device.

        :param name: Name of the interface.
        :param mac_address: MAC address of the interface.
        :param tags: Tags for the interface.
        :param vlan: Untagged VLAN the interface is connected to.  If not
            provided then the interface is considered disconnected.

        Following are extra parameters that can be set on the interface:

        :param mtu: Maximum transmission unit.
        :param accept_ra: Accept router advertisements. (IPv6 only)
        :param autoconf: Perform stateless autoconfiguration. (IPv6 only)

        Returns 404 if the node is not found.
        """
        node = Node.objects.get_node_or_404(
            system_id, request.user, NodePermission.edit)
        raise_error_if_controller(node, "create")
        # Machine type nodes require the node needs to be in the correct state
        # and that the user has admin permissions.
        if node.node_type == NODE_TYPE.MACHINE:
            if not request.user.has_perm(NodePermission.admin, node):
                raise MAASAPIForbidden()
            raise_error_for_invalid_state_on_allocated_operations(
                node, request.user, "create")
        form = PhysicalInterfaceForm(node=node, data=request.data)
        if form.is_valid():
            return form.save()
        else:
            # The Interface model validation is so strict that it will cause
            # the mac_address field to include two messages about it being
            # required. We clean up this response to not provide duplicate
            # information.
            if "mac_address" in form.errors:
                if (MISSING_FIELD in form.errors["mac_address"] and
                        BLANK_FIELD in form.errors["mac_address"]):
                    form.errors["mac_address"] = ErrorList([
                        error
                        for error in form.errors["mac_address"]
                        if error != BLANK_FIELD
                    ])
            raise MAASAPIValidationError(form.errors)
Exemple #4
0
    def update(self, request, id, file_id):
        """Upload piece of boot resource file."""
        resource = get_object_or_404(BootResource, id=id)
        rfile = get_object_or_404(BootResourceFile, id=file_id)
        size = int(request.META.get("CONTENT_LENGTH", "0"))
        data = request.body
        if size == 0:
            raise MAASAPIBadRequest("Missing data.")
        if size != len(data):
            raise MAASAPIBadRequest(
                "Content-Length doesn't equal size of recieved data."
            )
        if resource.rtype not in ALLOW_UPLOAD_RTYPES:
            raise MAASAPIForbidden(
                "Cannot upload to a resource of type: %s. " % resource.rtype
            )
        if rfile.largefile.complete:
            raise MAASAPIBadRequest("Cannot upload to a complete file.")

        with rfile.largefile.content.open("wb") as stream:
            stream.seek(0, os.SEEK_END)

            # Check that the uploading data will not make the file larger
            # than expected.
            current_size = stream.tell()
            if current_size + size > rfile.largefile.total_size:
                raise MAASAPIBadRequest("Too much data recieved.")

            stream.write(data)
            rfile.largefile.size = current_size + size
            rfile.largefile.save()

        if rfile.largefile.complete:
            if not rfile.largefile.valid:
                raise MAASAPIBadRequest(
                    "Saved content does not match given SHA256 value."
                )
            # Avoid circular import.
            from maasserver.clusterrpc.boot_images import (
                RackControllersImporter,
            )

            post_commit_do(RackControllersImporter.schedule)
        return rc.ALL_OK
Exemple #5
0
    def create(self, request):
        """@description-title Create an IP range
        @description Create a new IP range.

        @param (string) "type" [required=true] Type of this range. (``dynamic``
        or ``reserved``)

        @param (string) "start_ip" [required=true] Start IP address of this
        range (inclusive).

        @param (string) "end_ip" [required=true] End IP address of this range
        (inclusive).

        @param (string) "subnet" [required=true] Subnet associated with this
        range.

        @param (string) "comment" [required=false] A description of this range.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing the new IP
        range.
        @success-example "success-json" [exkey=ipranges-create] placeholder
        text

        @error (http-status-code) "403" 403
        @error (content) "no-perms" The user does not have the permissions
        required to create an IP range.
        """
        if ('type' in request.data
                and request.data['type'] == IPRANGE_TYPE.DYNAMIC
                and not request.user.is_superuser):
            raise MAASAPIForbidden("Unable to create dynamic IP range. "
                                   "You don't have sufficient privileges.")

        form = IPRangeForm(data=request.data, request=request)
        if form.is_valid():
            return form.save()
        else:
            raise MAASAPIValidationError(form.errors)
Exemple #6
0
    def create(self, request):
        """Create an IP range.

        :param type: Type of this range. (`dynamic` or `reserved`)
        :param start_ip: Start IP address of this range (inclusive).
        :param end_ip: End IP address of this range (inclusive).
        :param subnet: Subnet this range is associated with. (optional)
        :param comment: A description of this range. (optional)

        Returns 403 if standard users tries to create a dynamic IP range.
        """
        if ('type' in request.data
                and request.data['type'] == IPRANGE_TYPE.DYNAMIC
                and not request.user.is_superuser):
            raise MAASAPIForbidden("Unable to create dynamic IP range. "
                                   "You don't have sufficient privileges.")

        form = IPRangeForm(data=request.data, request=request)
        if form.is_valid():
            return form.save()
        else:
            raise MAASAPIValidationError(form.errors)
Exemple #7
0
    def get_object_by_specifiers_or_raise(self, specifiers, **kwargs):
        """Gets an object using the given specifier(s).

        If the specifier is empty, raises Http400.
        If multiple objects are returned, raises Http403.
        If the object cannot be found, raises Http404.

        :param:specifiers: unicode
        """
        object_name = get_model_object_name(self)
        if isinstance(specifiers, str):
            specifiers = specifiers.strip()
        if specifiers is None or specifiers == "":
            raise MAASAPIBadRequest("%s specifier required." % object_name)
        try:
            object = get_one(self.filter_by_specifiers(specifiers, **kwargs))
            if object is None:
                raise Http404('No %s matches the given query.' % object_name)
        except self.model.MultipleObjectsReturned:
            raise MAASAPIForbidden(
                "Too many %s objects match the given query." % object_name)
        return object
Exemple #8
0
    def dismiss(self, request, id):
        """@description-title Dismiss a notification
        @description Dismiss a notification with the given id.

        It is safe to call multiple times for the same notification.

        @param (int) "{id}" [required=true] The notification id.

        @success (http-status-code) "server-success" 200

        @error (http-status-code) "403" 403
        @error (content) "no-perms" The notification is not relevant to the
        invoking user.

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested notification is not found.
        @error-example "not-found"
            Not Found
        """
        notification = get_object_or_404(Notification, id=id)
        if notification.is_relevant_to(request.user):
            notification.dismiss(request.user)
        else:
            raise MAASAPIForbidden()
Exemple #9
0
    def read(self, request, id):
        """@description-title Read a notification
        @description Read a notification with the given id.

        @param (int) "{id}" [required=true] The notification id.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing the requested
        notification object.
        @success-example "success-json" [exkey=notifications-read-by-id]
        placeholder text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested notification is not found.
        @error-example "not-found"
            Not Found
        """
        notification = get_object_or_404(Notification, id=id)
        if notification.is_relevant_to(request.user):
            return notification
        elif request.user.is_superuser:
            return notification
        else:
            raise MAASAPIForbidden()
Exemple #10
0
def raise_error_if_not_owner(iprange, user):
    if not user.is_superuser and iprange.user_id != user.id:
        raise MAASAPIForbidden(
            "Unable to modify IP range. You don't own the IP range.")
Exemple #11
0
    def update(self, request, system_id, id):
        """@description-title Update an interface
        @description Update an interface with the given system_id and interface
        id.

        Note: machines must have a status of Ready or Broken to have access to
        all options. Machines with Deployed status can only have the name
        and/or mac_address updated for an interface. This is intented to allow
        a bad interface to be replaced while the machine remains deployed.

        @param (string) "{system_id}" [required=true] A system_id.

        @param (int) "{id}" [required=true] An interface id.

        @param (string) "name" [required=false] (Physical interfaces) Name of
        the interface.

        @param (string) "mac_address" [required=false] (Physical interfaces)
        MAC address of the interface.

        @param (string) "tags" [required=false] (Physical interfaces) Tags for
        the interface.

        @param (int) "vlan" [required=false] (Physical interfaces) Untagged
        VLAN id the interface is connected to.  If not set then the interface
        is considered disconnected.

        @param (string) "name" [required=false] (Bond interfaces) Name of the
        interface.

        @param (string) "mac_address" [required=false] (Bond interfaces) MAC
        address of the interface.

        @param (string) "tags" [required=false] (Bond interfaces) Tags for the
        interface.

        @param (int) "vlan" [required=false] (Bond interfaces) Untagged VLAN id
        the interface is connected to. If not set then the interface is
        considered disconnected.

        @param (int) "parents" [required=false] (Bond interfaces) Parent
        interface ids that make this bond.

        @param (string) "tags" [required=false] (VLAN interfaces) Tags for the
        interface.

        @param (int) "vlan" [required=false] (VLAN interfaces) Tagged VLAN id
        the interface is connected to.

        @param (int) "parent" [required=false] (VLAN interfaces) Parent
        interface ids for the VLAN interface.

        @param (string) "name" [required=false] (Bridge interfaces) Name of the
        interface.

        @param (string) "mac_address" [required=false] (Bridge interfaces) MAC
        address of the interface.

        @param (string) "tags" [required=false] (Bridge interfaces) Tags for
        the interface.

        @param (int) "vlan" [required=false] (Bridge interfaces) VLAN id the
        interface is connected to.

        @param (int) "parent" [required=false] (Bridge interfaces) Parent
        interface ids for this bridge interface.

        @param (string) "bridge_type" [required=false] (Bridge interfaces) Type
        of bridge to create. Possible values are: ``standard``, ``ovs``.

        @param (boolean) "bridge_stp" [required=false] (Bridge interfaces) Turn
        spanning tree protocol on or off.  (Default: False).

        @param (int) "bridge_fd" [required=false] (Bridge interfaces) Set
        bridge forward delay to time seconds.  (Default: 15).

        @param (int) "bond_miimon" [required=false] (Bonds) The link monitoring
        freqeuncy in milliseconds.  (Default: 100).

        @param (int) "bond_downdelay" [required=false] (Bonds) Specifies the
        time, in milliseconds, to wait before disabling a slave after a link
        failure has been detected.

        @param (int) "bond_updelay" [required=false] (Bonds) Specifies the
        time, in milliseconds, to wait before enabling a slave after a link
        recovery has been detected.

        @param (string) "bond_lacp_rate" [required=false] (Bonds) Option
        specifying the rate in which we'll ask our link partner to transmit
        LACPDU packets in 802.3ad mode.  Available options are ``fast`` or
        ``slow``.  (Default: ``slow``).

        @param (string) "bond_xmit_hash_policy" [required=false] (Bonds) The
        transmit hash policy to use for slave selection in balance-xor,
        802.3ad, and tlb modes.  Possible values are: ``layer2``, ``layer2+3``,
        ``layer3+4``, ``encap2+3``, ``encap3+4``.

        @param (string) "bond_mode" [required=false,formatting=true] (Bonds)
        The operating mode of the bond.  (Default: ``active-backup``).

        Supported bonding modes (bond-mode):

        - ``balance-rr``: Transmit packets in sequential order from the first
          available slave through the last. This mode provides load balancing
          and fault tolerance.

        - ``active-backup``: Only one slave in the bond is active. A different
          slave becomes active if, and only if, the active slave fails. The
          bond's MAC address is externally visible on only one port (network
          adapter) to avoid confusing the switch.

        - ``balance-xor``: Transmit based on the selected transmit hash policy.
          The default policy is a simple [(source MAC address XOR'd with
          destination MAC address XOR packet type ID) modulo slave count].

        - ``broadcast``: Transmits everything on all slave interfaces. This
          mode provides fault tolerance.

        - ``802.3ad``: IEEE 802.3ad Dynamic link aggregation. Creates
          aggregation groups that share the same speed and duplex settings.
          Utilizes all slaves in the active aggregator according to the 802.3ad
          specification.

        - ``balance-tlb``: Adaptive transmit load balancing: channel bonding
          that does not require any special switch support.

        - ``balance-alb``: Adaptive load balancing: includes balance-tlb plus
          receive load balancing (rlb) for IPV4 traffic, and does not require
          any special switch support. The receive load balancing is achieved by
          ARP negotiation.

        @param (string) "mtu" [required=false] Maximum transmission unit.

        @param (string) "accept_ra" [required=false] Accept router
        advertisements. (IPv6 only)

        @param (string) "autoconf" [required=false] Perform stateless
        autoconfiguration. (IPv6 only)

        @param (boolean) "link_connected" [required=false]
        (Physical interfaces) Whether or not the interface is physically
        conntected to an uplink.  (Default: True).

        @param (int) "interface_speed" [required=false] (Physical interfaces)
        The speed of the interface in Mbit/s. (Default: 0).

        @param (int) "link_speed" [required=false] (Physical interfaces)
        The speed of the link in Mbit/s. (Default: 0).

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing the new
        requested interface object.
        @success-example "success-json" [exkey=interfaces-update] placeholder
        text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested machine or interface is not
        found.
        @error-example "not-found"
            Not Found
        """
        interface = Interface.objects.get_interface_or_404(
            system_id, id, request.user,
            NodePermission.admin, NodePermission.edit)
        node = interface.get_node()
        if node.node_type == NODE_TYPE.MACHINE:
            # This node needs to be in the correct state to modify
            # the interface.
            raise_error_for_invalid_state_on_allocated_operations(
                node, request.user, "update interface",
                extra_states=[NODE_STATUS.DEPLOYED])
        if node.is_controller:
            if interface.type == INTERFACE_TYPE.VLAN:
                raise MAASAPIForbidden(
                    "Cannot modify VLAN interface on controller.")
            interface_form = ControllerInterfaceForm
        elif node.status == NODE_STATUS.DEPLOYED:
            interface_form = DeployedInterfaceForm
        else:
            interface_form = InterfaceForm.get_interface_form(interface.type)
        # For VLAN interface we cast parents to parent. As a VLAN can only
        # have one parent.
        if interface.type == INTERFACE_TYPE.VLAN:
            request.data = request.data.copy()
            if 'parent' in request.data:
                request.data['parents'] = request.data['parent']
        form = interface_form(instance=interface, data=request.data)
        if form.is_valid():
            return form.save()
        else:
            # Replace parents with parent so it matches the API parameter, if
            # the interface being editted was a VLAN interface.
            if (interface.type == INTERFACE_TYPE.VLAN and
                    'parents' in form.errors):
                form.errors['parent'] = form.errors.pop('parents')
            raise MAASAPIValidationError(form.errors)
Exemple #12
0
    def create_physical(self, request, system_id):
        """@description-title Create a physical interface
        @description Create a physical interface on a machine and device.

        @param (string) "{system_id}" [required=true] A system_id.

        @param (string) "name" [required=false] Name of the interface.

        @param (string) "mac_address" [required=true] MAC address of the
        interface.

        @param (string) "tags" [required=false] Tags for the interface.

        @param (string) "vlan" [required=false] Untagged VLAN the interface is
        connected to. If not provided then the interface is considered
        disconnected.

        @param (int) "mtu" [required=false] Maximum transmission unit.

        @param (boolean) "accept_ra" [required=false] Accept router
        advertisements. (IPv6 only)

        @param (boolean) "autoconf" [required=false] Perform stateless
        autoconfiguration. (IPv6 only)

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing the new
        interface object.
        @success-example "success-json" [exkey=interfaces-create-physical]
        placeholder text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested machine is not found.
        @error-example "not-found"
            Not Found
        """
        node = Node.objects.get_node_or_404(
            system_id, request.user, NodePermission.edit)
        raise_error_if_controller(node, "create")
        # Machine type nodes require the node needs to be in the correct state
        # and that the user has admin permissions.
        if node.node_type == NODE_TYPE.MACHINE:
            if not request.user.has_perm(NodePermission.admin, node):
                raise MAASAPIForbidden()
            raise_error_for_invalid_state_on_allocated_operations(
                node, request.user, "create")
        form = PhysicalInterfaceForm(node=node, data=request.data)
        if form.is_valid():
            return form.save()
        else:
            # The Interface model validation is so strict that it will cause
            # the mac_address field to include two messages about it being
            # required. We clean up this response to not provide duplicate
            # information.
            if "mac_address" in form.errors:
                if (MISSING_FIELD in form.errors["mac_address"] and
                        BLANK_FIELD in form.errors["mac_address"]):
                    form.errors["mac_address"] = ErrorList([
                        error
                        for error in form.errors["mac_address"]
                        if error != BLANK_FIELD
                    ])
            raise MAASAPIValidationError(form.errors)
Exemple #13
0
def raise_error_if_controller(node, operation):
    if node.is_controller:
        raise MAASAPIForbidden(
            "Cannot %s interface on a controller." % operation)
Exemple #14
0
    def update(self, request, system_id, id):
        """Update interface on node.

        Machines must have a status of Ready or Broken to have access to all
        options. Machines with Deployed status can only have the name and/or
        mac_address updated for an interface. This is intented to allow a bad
        interface to be replaced while the machine remains deployed.

        Fields for physical interface:

        :param name: Name of the interface.
        :param mac_address: MAC address of the interface.
        :param tags: Tags for the interface.
        :param vlan: Untagged VLAN the interface is connected to.  If not set
            then the interface is considered disconnected.

        Fields for bond interface:

        :param name: Name of the interface.
        :param mac_address: MAC address of the interface.
        :param tags: Tags for the interface.
        :param vlan: Untagged VLAN the interface is connected to.  If not set
            then the interface is considered disconnected.
        :param parents: Parent interfaces that make this bond.

        Fields for VLAN interface:

        :param tags: Tags for the interface.
        :param vlan: Tagged VLAN the interface is connected to.
        :param parent: Parent interface for this VLAN interface.

        Fields for bridge interface:

        :param name: Name of the interface.
        :param mac_address: MAC address of the interface.
        :param tags: Tags for the interface.
        :param vlan: VLAN the interface is connected to.
        :param parent: Parent interface for this bridge interface.

        Following are extra parameters that can be set on all interface types:

        :param mtu: Maximum transmission unit.
        :param accept_ra: Accept router advertisements. (IPv6 only)
        :param autoconf: Perform stateless autoconfiguration. (IPv6 only)

        Following are parameters specific to bonds:

        :param bond_mode: The operating mode of the bond.
            (Default: active-backup).
        :param bond_miimon: The link monitoring freqeuncy in milliseconds.
            (Default: 100).
        :param bond_downdelay: Specifies the time, in milliseconds, to wait
            before disabling a slave after a link failure has been detected.
        :param bond_updelay: Specifies the time, in milliseconds, to wait
            before enabling a slave after a link recovery has been detected.
        :param bond_lacp_rate: Option specifying the rate in which we'll ask
            our link partner to transmit LACPDU packets in 802.3ad mode.
            Available options are fast or slow. (Default: slow).
        :param bond_xmit_hash_policy: The transmit hash policy to use for
            slave selection in balance-xor, 802.3ad, and tlb modes.

        Supported bonding modes (bond-mode):

        balance-rr - Transmit packets in sequential order from the first
        available slave through the last.  This mode provides load balancing
        and fault tolerance.

        active-backup - Only one slave in the bond is active.  A different
        slave becomes active if, and only if, the active slave fails.  The
        bond's MAC address is externally visible on only one port (network
        adapter) to avoid confusing the switch.

        balance-xor - Transmit based on the selected transmit hash policy.
        The default policy is a simple [(source MAC address XOR'd with
        destination MAC address XOR packet type ID) modulo slave count].

        broadcast - Transmits everything on all slave interfaces. This mode
        provides fault tolerance.

        802.3ad - IEEE 802.3ad Dynamic link aggregation.  Creates aggregation
        groups that share the same speed and duplex settings.  Utilizes all
        slaves in the active aggregator according to the 802.3ad specification.

        balance-tlb - Adaptive transmit load balancing: channel bonding that
        does not require any special switch support.

        balance-alb - Adaptive load balancing: includes balance-tlb plus
        receive load balancing (rlb) for IPV4 traffic, and does not require any
        special switch support.  The receive load balancing is achieved by
        ARP negotiation.

        Following are parameters specific to bridges:

        :param bridge_stp: Turn spanning tree protocol on or off.
            (Default: False).
        :param bridge_fd: Set bridge forward delay to time seconds.
            (Default: 15).

        Returns 404 if the node or interface is not found.
        """
        interface = Interface.objects.get_interface_or_404(
            system_id, id, request.user, NODE_PERMISSION.EDIT)
        node = interface.get_node()
        if node.node_type == NODE_TYPE.MACHINE:
            # This node needs to be in the correct state to modify
            # the interface.
            raise_error_for_invalid_state_on_allocated_operations(
                node,
                request.user,
                "update interface",
                extra_states=[NODE_STATUS.DEPLOYED])
        if node.is_controller:
            if interface.type == INTERFACE_TYPE.VLAN:
                raise MAASAPIForbidden(
                    "Cannot modify VLAN interface on controller.")
            interface_form = ControllerInterfaceForm
        elif node.status == NODE_STATUS.DEPLOYED:
            interface_form = DeployedInterfaceForm
        else:
            interface_form = InterfaceForm.get_interface_form(interface.type)
        # For VLAN interface we cast parents to parent. As a VLAN can only
        # have one parent.
        if interface.type == INTERFACE_TYPE.VLAN:
            request.data = request.data.copy()
            if 'parent' in request.data:
                request.data['parents'] = request.data['parent']
        form = interface_form(instance=interface, data=request.data)
        if form.is_valid():
            return form.save()
        else:
            # Replace parents with parent so it matches the API parameter, if
            # the interface being editted was a VLAN interface.
            if (interface.type == INTERFACE_TYPE.VLAN
                    and 'parents' in form.errors):
                form.errors['parent'] = form.errors.pop('parents')
            raise MAASAPIValidationError(form.errors)
Exemple #15
0
    def _claim_ip(self, user, subnet, ip_address, mac=None, hostname=None):
        """Attempt to get a USER_RESERVED StaticIPAddress for `user`.

        :param subnet: Subnet to use use for claiming the IP.
        :param ip_address: Any requested address
        :param mac: MAC address to use
        :param hostname: The hostname
        :param domain: The domain to use
        :type subnet: Subnet
        :type ip_address: str
        :type mac: str
        :type hostname: str
        :type domain: Domain
        :raises StaticIPAddressExhaustion: If no IPs available.
        """
        dynamic_range = subnet.get_dynamic_maasipset()
        if ip_address is not None and ip_address in dynamic_range:
            raise MAASAPIForbidden(
                "IP address %s belongs to an existing dynamic range. To "
                "reserve this IP address, a MAC address is required. (Create "
                "a device instead.)" % ip_address)
        if hostname is not None and hostname.find('.') > 0:
            hostname, domain = hostname.split('.', 1)
            domain = Domain.objects.get_domain_or_404("name:%s" % domain, user,
                                                      NODE_PERMISSION.VIEW)
        else:
            domain = Domain.objects.get_default_domain()
        if mac is None:
            sip = StaticIPAddress.objects.allocate_new(
                alloc_type=IPADDRESS_TYPE.USER_RESERVED,
                requested_address=ip_address,
                subnet=subnet,
                user=user)
            if hostname is not None and hostname != '':
                dnsrr, _ = DNSResource.objects.get_or_create(name=hostname,
                                                             domain=domain)
                dnsrr.ip_addresses.add(sip)
            maaslog.info("User %s was allocated IP %s", user.username, sip.ip)
        else:
            # The user has requested a static IP linked to a MAC address, so we
            # set that up via the Interface model. If the MAC address is part
            # of a node, then this operation is not allowed. The 'link_subnet'
            # API should be used on that interface.
            nic, created = (Interface.objects.get_or_create(
                mac_address=mac,
                defaults={
                    'type': INTERFACE_TYPE.UNKNOWN,
                    'name': 'eth0',
                }))
            if nic.type != INTERFACE_TYPE.UNKNOWN:
                raise MAASAPIBadRequest(
                    "MAC address %s already belongs to %s. Use of the "
                    "interface API is required, for an interface that belongs "
                    "to a node." % (nic.mac_address, nic.node.hostname))

            # Link the new interface on the same subnet as the
            # ip_address.
            sip = nic.link_subnet(INTERFACE_LINK_TYPE.STATIC,
                                  subnet,
                                  ip_address=ip_address,
                                  alloc_type=IPADDRESS_TYPE.USER_RESERVED,
                                  user=user)
            if hostname is not None and hostname != '':
                dnsrr, _ = DNSResource.objects.get_or_create(name=hostname,
                                                             domain=domain)
                dnsrr.ip_addresses.add(sip)
            maaslog.info("User %s was allocated IP %s for MAC address %s",
                         user.username, sip.ip, nic.mac_address)
        return sip