Example #1
0
def filtered_nodes_list_from_request(request, model=None):
    """List Nodes visible to the user, optionally filtered by criteria.

    Nodes are sorted by id (i.e. most recent last).

    :param hostname: An optional hostname. Only events relating to the node
        with the matching hostname will be returned. This can be specified
        multiple times to get events relating to more than one node.
    :param mac_address: An optional MAC address. Only events relating to the
        node owning the specified MAC address will be returned. This can be
        specified multiple times to get events relating to more than one node.
    :param id: An optional list of system ids.  Only events relating to the
        nodes with matching system ids will be returned.
    :param domain: An optional name for a dns domain. Only events relating to
        the nodes in the domain will be returned.
    :param zone: An optional name for a physical zone. Only events relating to
        the nodes in the zone will be returned.
    :param pool: An optional name for a resource pool. Only nodes belonging
        to the pool will be returned.
    :param agent_name: An optional agent name.  Only events relating to the
        nodes with matching agent names will be returned.
    """
    # Get filters from request.
    match_ids = get_optional_list(request.GET, "id")

    match_macs = get_optional_list(request.GET, "mac_address")
    if match_macs is not None:
        invalid_macs = [mac for mac in match_macs if MAC_RE.match(mac) is None]
        if len(invalid_macs) != 0:
            raise MAASAPIValidationError("Invalid MAC address(es): %s" %
                                         ", ".join(invalid_macs))

    if model is None:
        model = Node
    # Fetch nodes and apply filters.
    nodes = model.objects.get_nodes(request.user,
                                    NodePermission.view,
                                    ids=match_ids)
    if match_macs is not None:
        nodes = nodes.filter(interface__mac_address__in=match_macs)
    match_hostnames = get_optional_list(request.GET, "hostname")
    if match_hostnames is not None:
        nodes = nodes.filter(hostname__in=match_hostnames)
    match_domains = get_optional_list(request.GET, "domain")
    if match_domains is not None:
        nodes = nodes.filter(domain__name__in=match_domains)
    match_zone_name = request.GET.get("zone", None)
    if match_zone_name is not None:
        nodes = nodes.filter(zone__name=match_zone_name)
    match_pool_name = request.GET.get("pool", None)
    if match_pool_name is not None:
        nodes = nodes.filter(pool__name=match_pool_name)
    match_agent_name = request.GET.get("agent_name", None)
    if match_agent_name is not None:
        nodes = nodes.filter(agent_name=match_agent_name)

    return nodes.order_by("id")
Example #2
0
    def list(self, request):
        """List Nodes visible to the user, optionally filtered by criteria.

        :param hostname: An optional list of hostnames.  Only nodes with
            matching hostnames will be returned.
        :type hostname: iterable
        :param mac_address: An optional list of MAC addresses.  Only
            nodes with matching MAC addresses will be returned.
        :type mac_address: iterable
        :param id: An optional list of system ids.  Only nodes with
            matching system ids will be returned.
        :type id: iterable
        :param zone: An optional name for a physical zone. Only nodes in the
            zone will be returned.
        :type zone: unicode
        :param agent_name: An optional agent name.  Only nodes with
            matching agent names will be returned.
        :type agent_name: unicode
        """
        # Get filters from request.
        match_ids = get_optional_list(request.GET, 'id')
        match_macs = get_optional_list(request.GET, 'mac_address')
        if match_macs is not None:
            invalid_macs = [
                mac for mac in match_macs if MAC_RE.match(mac) is None]
            if len(invalid_macs) != 0:
                raise ValidationError(
                    "Invalid MAC address(es): %s" % ", ".join(invalid_macs))

        # Fetch nodes and apply filters.
        nodes = Node.objects.get_nodes(
            request.user, NODE_PERMISSION.VIEW, ids=match_ids)
        if match_macs is not None:
            nodes = nodes.filter(macaddress__mac_address__in=match_macs)
        match_hostnames = get_optional_list(request.GET, 'hostname')
        if match_hostnames is not None:
            nodes = nodes.filter(hostname__in=match_hostnames)
        match_zone_name = request.GET.get('zone', None)
        if match_zone_name is not None:
            nodes = nodes.filter(zone__name=match_zone_name)
        match_agent_name = request.GET.get('agent_name', None)
        if match_agent_name is not None:
            nodes = nodes.filter(agent_name=match_agent_name)

        # Prefetch related objects that are needed for rendering the result.
        nodes = nodes.prefetch_related('macaddress_set__node')
        nodes = nodes.prefetch_related('macaddress_set__ip_addresses')
        nodes = nodes.prefetch_related('tags')
        nodes = nodes.select_related('nodegroup')
        nodes = nodes.prefetch_related('nodegroup__dhcplease_set')
        nodes = nodes.prefetch_related('nodegroup__nodegroupinterface_set')
        nodes = nodes.prefetch_related('zone')
        return nodes.order_by('id')
Example #3
0
    def set_zone(self, request):
        """@description-title Assign nodes to a zone
        @description Assigns a given node to a given zone.

        @param (string) "zone" [required=true] The zone name.
        @param (string) "nodes" [required=true] The node to add.

        @success (http-status-code) "204" 204

        @error (http-status-code) "403" 403
        @error (content) "no-perms" The user does not have set the zone.
        @error-example "no-perms"
            This method is reserved for admin users.

        @error (http-status-code) "400" 400
        @error (content) "bad-param" The given parameters were not correct.
        """
        data = {
            "zone": request.data.get("zone"),
            "system_id": get_optional_list(request.data, "nodes"),
        }
        form = BulkNodeSetZoneForm(request.user, data=data)
        if not form.is_valid():
            raise MAASAPIValidationError(form.errors)
        form.save()
Example #4
0
    def power_parameters(self, request):
        """@description-title Get power parameters
        @description Get power parameters for multiple machines. To request
        power parameters for a specific machine or more than one machine:
        ``op=power_parameters&id=abc123&id=def456``.

        @param (url-string) "id" [required=false] A system ID. To request more
        than one machine, provide multiple ``id`` arguments in the request.
        Only machines with matching system ids will be returned.
        @param-example "id"
            op=power_parameters&id=abc123&id=def456

        @success (http-status-code) "200" 200
        @success (json) "success_json" A JSON object containing a list of
        power parameters with system_ids as keys.
        @success-example "success_json" [exkey=get-power-params] placeholder
        text

        @error (http-status-code) "403" 403
        @error (content) "no-perms" The user is not authorized to view the
        power parameters.
        """
        match_ids = get_optional_list(request.GET, "id")

        if match_ids is None:
            machines = self.base_model.objects.all()
        else:
            machines = self.base_model.objects.filter(system_id__in=match_ids)

        return {
            machine.system_id: machine.power_parameters
            for machine in machines
        }
Example #5
0
    def set_zone(self, request):
        """Assign multiple nodes to a physical zone at once.

        :param zone: Zone name.  If omitted, the zone is "none" and the nodes
            will be taken out of their physical zones.
        :param nodes: system_ids of the nodes whose zones are to be set.
           (An empty list is acceptable).

        Raises 403 if the user is not an admin.
        """
        data = {
            'action': 'set_zone',
            'zone': request.data.get('zone'),
            'system_id': get_optional_list(request.data, 'nodes'),
        }
        form = BulkNodeActionForm(request.user, data=data)
        if not form.is_valid():
            raise MAASAPIValidationError(form.errors)
        form.save()
Example #6
0
    def power_parameters(self, request):
        """Retrieve power parameters for multiple nodes.

        :param id: An optional list of system ids.  Only nodes with
            matching system ids will be returned.
        :type id: iterable

        :return: A dictionary of power parameters, keyed by node system_id.

        Raises 403 if the user is not an admin.
        """
        match_ids = get_optional_list(request.GET, 'id')

        if match_ids is None:
            nodes = Node.objects.all()
        else:
            nodes = Node.objects.filter(system_id__in=match_ids)

        return {node.system_id: node.power_parameters for node in nodes}
Example #7
0
    def set_zone(self, request):
        """Assign multiple nodes to a physical zone at once.

        :param zone: Zone name.  If omitted, the zone is "none" and the nodes
            will be taken out of their physical zones.
        :param nodes: system_ids of the nodes whose zones are to be set.
           (An empty list is acceptable).

        Raises 403 if the user is not an admin.
        """
        data = {
            'action': 'set_zone',
            'zone': request.data.get('zone'),
            'system_id': get_optional_list(request.data, 'nodes'),
        }
        form = BulkNodeActionForm(request.user, data=data)
        if not form.is_valid():
            raise ValidationError(form.errors)
        form.save()
Example #8
0
    def power_parameters(self, request):
        """Retrieve power parameters for multiple machines.

        :param id: An optional list of system ids.  Only machines with
            matching system ids will be returned.
        :type id: iterable

        :return: A dictionary of power parameters, keyed by machine system_id.

        Raises 403 if the user is not an admin.
        """
        match_ids = get_optional_list(request.GET, 'id')

        if match_ids is None:
            machines = self.base_model.objects.all()
        else:
            machines = self.base_model.objects.filter(system_id__in=match_ids)

        return {machine.system_id: machine.power_parameters
                for machine in machines}
Example #9
0
    def scan(self, request, **kwargs):
        """@description-title Run discovery scan on rack networks
        @description Immediately run a neighbour discovery scan on all rack
        networks.

        This command causes each connected rack controller to execute the
        'maas-rack scan-network' command, which will scan all CIDRs configured
        on the rack controller using 'nmap' (if it is installed) or 'ping'.

        Network discovery must not be set to 'disabled' for this command to be
        useful.

        Scanning will be started in the background, and could take a long time
        on rack controllers that do not have 'nmap' installed and are connected
        to large networks.

        If the call is a success, this method will return a dictionary of
        results with the following keys:

        ``result``: A human-readable string summarizing the results.

        ``scan_attempted_on``: A list of rack system_id values where a scan
        was attempted. (That is, an RPC connection was successful and a
        subsequent call was intended.)

        ``failed_to_connect_to``: A list of rack system_id values where the
        RPC connection failed.

        ``scan_started_on``: A list of rack system_id values where a scan was
        successfully started.

        ``scan_failed_on``: A list of rack system_id values where a scan was
        attempted, but failed because a scan was already in progress.

        ``rpc_call_timed_out_on``: A list of rack system_id values where the
        RPC connection was made, but the call timed out before a ten second
        timeout elapsed.

        @param (string) "cidr" [required=false] The subnet CIDR(s) to scan (can
        be specified multiple times). If not specified, defaults to all
        networks.

        @param (boolean) "force" [required=false] If True, will force the scan,
        even if all networks are specified. (This may not be the best idea,
        depending on acceptable use agreements, and the politics of the
        organization that owns the network.) Note that this parameter is
        required if all networks are specified. Default: False.

        @param (string) "always_use_ping" [required=false] If True, will force
        the scan to use 'ping' even if 'nmap' is installed. Default: False.

        @param (string) "slow" [required=false] If True, and 'nmap' is being
        used, will limit the scan to nine packets per second. If the scanner is
        'ping', this option has no effect. Default: False.

        @param (string) "threads" [required=false] The number of threads to use
        during scanning. If 'nmap' is the scanner, the default is one thread
        per 'nmap' process. If 'ping' is the scanner, the default is four
        threads per CPU.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing a dictionary of
        results.
        @success-example "success-json" [exkey=discovery-scan]
        placeholder text
        """
        cidrs = get_optional_list(request.POST,
                                  'cidr',
                                  default=[],
                                  validator=CIDR)
        # The RPC call requires a list of CIDRs.
        ipnetworks = [IPNetwork(cidr) for cidr in cidrs]
        force = get_optional_param(request.POST,
                                   'force',
                                   default=False,
                                   validator=StringBool)
        always_use_ping = get_optional_param(request.POST,
                                             'always_use_ping',
                                             default=False,
                                             validator=StringBool)
        slow = get_optional_param(request.POST,
                                  'slow',
                                  default=False,
                                  validator=StringBool)
        threads = get_optional_param(request.POST,
                                     'threads',
                                     default=None,
                                     validator=Number)
        if threads is not None:
            threads = int(threads)  # Could be a floating point.
        if len(cidrs) == 0 and force is not True:
            error = (
                "Bad request: scanning all subnets is not allowed unless "
                "force=True is specified.\n\n**WARNING: This will scan ALL "
                "networks attached to MAAS rack controllers.\nCheck with your "
                "internet service provider or IT department to be sure this "
                "is\nallowed before proceeding.**\n")
            return HttpResponseBadRequest(content_type="text/plain",
                                          content=error)
        elif len(cidrs) == 0 and force is True:
            # No CIDRs specified and force==True, so scan all networks.
            results = scan_all_rack_networks(scan_all=True,
                                             ping=always_use_ping,
                                             slow=slow,
                                             threads=threads)
        else:
            results = scan_all_rack_networks(cidrs=ipnetworks,
                                             ping=always_use_ping,
                                             slow=slow,
                                             threads=threads)
        return user_friendly_scan_results(results)
Example #10
0
    def read(self, request):
        """@description-title Read commissioning results
        @description Read the commissioning results per node visible to the
        user, optionally filtered.

        @param (string) "system_id" [required=false] An optional list of system
        ids. Only the results related to the nodes with these system ids will
        be returned.

        @param (string) "name" [required=false] An optional list of names.
        Only the results with the specified names will be returned.

        @param (string) "result_type" [required=false] An optional result_type.
        Only the results with the specified result_type will be returned.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing a list of the
        requested installation-result objects.
        @success-example "success-json" [exkey=installation-results]
        placeholder text
        """
        # Get filters from request.
        system_ids = get_optional_list(request.GET, "system_id")
        names = get_optional_list(request.GET, "name")
        result_type = get_optional_param(request.GET, "result_type", None, Int)
        nodes = Node.objects.get_nodes(request.user,
                                       NodePermission.view,
                                       ids=system_ids)
        script_sets = []
        for node in nodes:
            if node.current_commissioning_script_set is not None:
                script_sets.append(node.current_commissioning_script_set)
            if node.current_installation_script_set is not None:
                script_sets.append(node.current_installation_script_set)
            if node.current_testing_script_set is not None:
                script_sets.append(node.current_testing_script_set)

        if names is not None:
            # Convert to a set; it's used for membership testing.
            names = set(names)

        resource_uri = reverse("commissioning_scripts_handler")

        results = []
        for script_set in script_sets:
            if (result_type is not None
                    and script_set.result_type != result_type):
                continue
            for script_result in script_set.scriptresult_set.filter(
                    status__in=(
                        SCRIPT_STATUS.PASSED,
                        SCRIPT_STATUS.FAILED,
                        SCRIPT_STATUS.TIMEDOUT,
                        SCRIPT_STATUS.ABORTED,
                    )):
                if names is not None and script_result.name not in names:
                    continue
                # MAAS stores stdout, stderr, and the combined output. The
                # metadata API determine which field uploaded data should go
                # into based on the extention of the uploaded file. .out goes
                # to stdout, .err goes to stderr, otherwise its assumed the
                # data is combined. Curtin uploads the installation log as
                # install.log so its stored as a combined result. This ensures
                # a result is always returned.
                if script_result.stdout != b"":
                    data = b64encode(script_result.stdout)
                else:
                    data = b64encode(script_result.output)
                results.append({
                    "created": script_result.created,
                    "updated": script_result.updated,
                    "id": script_result.id,
                    "name": script_result.name,
                    "script_result": script_result.exit_status,
                    "result_type": script_set.result_type,
                    "node": {
                        "system_id": script_set.node.system_id
                    },
                    "data": data,
                    "resource_uri": resource_uri,
                })
                if script_result.stderr != b"":
                    results.append({
                        "created": script_result.created,
                        "updated": script_result.updated,
                        "id": script_result.id,
                        "name": "%s.err" % script_result.name,
                        "script_result": script_result.exit_status,
                        "result_type": script_set.result_type,
                        "node": {
                            "system_id": script_set.node.system_id
                        },
                        "data": b64encode(script_result.stderr),
                        "resource_uri": resource_uri,
                    })

        return results
Example #11
0
    def read(self, request):
        """List NodeResult visible to the user, optionally filtered.

        :param system_id: An optional list of system ids.  Only the
            results related to the nodes with these system ids
            will be returned.
        :type system_id: iterable
        :param name: An optional list of names.  Only the results
            with the specified names will be returned.
        :type name: iterable
        :param result_type: An optional result_type.  Only the results
            with the specified result_type will be returned.
        :type name: iterable
        """
        # Get filters from request.
        system_ids = get_optional_list(request.GET, 'system_id')
        names = get_optional_list(request.GET, 'name')
        result_type = get_optional_param(request.GET, 'result_type', None, Int)
        nodes = Node.objects.get_nodes(request.user,
                                       NODE_PERMISSION.VIEW,
                                       ids=system_ids)
        script_sets = []
        for node in nodes:
            if node.current_commissioning_script_set is not None:
                script_sets.append(node.current_commissioning_script_set)
            if node.current_installation_script_set is not None:
                script_sets.append(node.current_installation_script_set)
            if node.current_testing_script_set is not None:
                script_sets.append(node.current_testing_script_set)

        if names is not None:
            # Convert to a set; it's used for membership testing.
            names = set(names)

        resource_uri = reverse('commissioning_scripts_handler')

        results = []
        for script_set in script_sets:
            if (result_type is not None
                    and script_set.result_type != result_type):
                continue
            for script_result in script_set.scriptresult_set.filter(
                    status__in=(SCRIPT_STATUS.PASSED, SCRIPT_STATUS.FAILED,
                                SCRIPT_STATUS.TIMEDOUT,
                                SCRIPT_STATUS.ABORTED)):
                if names is not None and script_result.name not in names:
                    continue
                # MAAS stores stdout, stderr, and the combined output. The
                # metadata API determine which field uploaded data should go
                # into based on the extention of the uploaded file. .out goes
                # to stdout, .err goes to stderr, otherwise its assumed the
                # data is combined. Curtin uploads the installation log as
                # install.log so its stored as a combined result. This ensures
                # a result is always returned.
                if script_result.stdout != b'':
                    data = b64encode(script_result.stdout)
                else:
                    data = b64encode(script_result.output)
                results.append({
                    'created': script_result.created,
                    'updated': script_result.updated,
                    'id': script_result.id,
                    'name': script_result.name,
                    'script_result': script_result.exit_status,
                    'result_type': script_set.result_type,
                    'node': {
                        'system_id': script_set.node.system_id
                    },
                    'data': data,
                    'resource_uri': resource_uri,
                })
                if script_result.stderr != b'':
                    results.append({
                        'created': script_result.created,
                        'updated': script_result.updated,
                        'id': script_result.id,
                        'name': '%s.err' % script_result.name,
                        'script_result': script_result.exit_status,
                        'result_type': script_set.result_type,
                        'node': {
                            'system_id': script_set.node.system_id
                        },
                        'data': b64encode(script_result.stderr),
                        'resource_uri': resource_uri,
                    })

        return results
Example #12
0
 def list_allocated(self, request):
     """Fetch Nodes that were allocated to the User/oauth token."""
     token = get_oauth_token(request)
     match_ids = get_optional_list(request.GET, 'id')
     nodes = Node.objects.get_allocated_visible_nodes(token, match_ids)
     return nodes.order_by('id')