Exemple #1
0
 def _power_control_seamicro15k_ipmi(self, ip, username, password,
                                     server_id, power_change):
     """Power on/off SeaMicro node via ipmitool."""
     power_mode = 1 if power_change == 'on' else 6
     try:
         call_and_check([
             'ipmitool',
             '-I',
             'lanplus',
             '-H',
             ip,
             '-U',
             username,
             '-P',
             password,
             'raw',
             '0x2E',
             '1',
             '0x00',
             '0x7d',
             '0xab',
             power_mode,
             '0',
             server_id,
         ])
     except ExternalProcessError as e:
         raise PowerActionError(
             "Failed to power %s %s at %s: %s" %
             (power_change, server_id, ip, e.output_as_unicode))
Exemple #2
0
 def test__passes_timeout_to_communicate(self):
     command = factory.make_name("command")
     process = self.patch_popen()
     timeout = random.randint(1, 10)
     call_and_check(command, timeout=timeout)
     self.assertThat(process.communicate,
                     MockCalledOnceWith(timeout=timeout))
Exemple #3
0
def validate(server,
             failover_peers,
             shared_networks,
             hosts,
             interfaces,
             global_dhcp_snippets=None):
    """Validate the DHCPv6/DHCPv4 configuration.

    :param server: A `DHCPServer` instance.
    :param failover_peers: List of dicts with failover parameters for each
        subnet where HA is enabled.
    :param shared_networks: List of dicts with shared network parameters that
        contain a list of subnets when the DHCP should server shared.
        If no shared network are defined, the DHCP server will be stopped.
    :param hosts: List of dicts with host parameters that
        contain a list of hosts the DHCP should statically.
    :param interfaces: List of interfaces that DHCP should use.
    :param global_dhcp_snippets: List of all global DHCP snippets
    """
    if global_dhcp_snippets is None:
        global_dhcp_snippets = []
    state = DHCPState(server.omapi_key, failover_peers, shared_networks, hosts,
                      interfaces, global_dhcp_snippets)
    dhcpd_config, _ = state.get_config(server)
    with NamedTemporaryFile(prefix='maas-dhcpd-') as tmp_dhcpd:
        tmp_dhcpd.file.write(dhcpd_config.encode('utf-8'))
        tmp_dhcpd.file.flush()
        try:
            call_and_check(['dhcpd', '-t', '-cf', tmp_dhcpd.name])
        except ExternalProcessError as e:
            return _parse_dhcpd_errors(e.output_as_unicode)
    return None
Exemple #4
0
def update_targets_conf(snapshot):
    """Runs tgt-admin to update the new targets from "maas.tgt"."""
    # Ensure that tgt is running before tgt-admin is used.
    service_monitor.ensureService("tgt").wait(30)

    # Update the tgt config.
    targets_conf = os.path.join(snapshot, 'maas.tgt')

    # The targets_conf may not exist in the event the BootSource is broken
    # and images havn't been imported yet. This fixes LP:1655721
    if not os.path.exists(targets_conf):
        return

    try:
        call_and_check(
            sudo([
                get_path('/usr/sbin/tgt-admin'),
                '--conf',
                targets_conf,
                '--update',
                'ALL',
            ]))
    except ExternalProcessError as e:
        msg = "Unable to update TGT config: %s" % e
        try_send_rack_event(EVENT_TYPES.RACK_IMPORT_WARNING, msg)
        maaslog.warning(msg)
Exemple #5
0
 def _power_control_seamicro15k_ipmi(self, ip, username, password,
                                     server_id, power_change):
     """Power on/off SeaMicro node via ipmitool."""
     power_mode = 1 if power_change == "on" else 6
     try:
         call_and_check([
             "ipmitool",
             "-I",
             "lanplus",
             "-H",
             ip,
             "-U",
             username,
             "-P",
             password,
             "-L",
             "OPERATOR",
             "raw",
             "0x2E",
             "1",
             "0x00",
             "0x7d",
             "0xab",
             power_mode,
             "0",
             server_id,
         ])
     except ExternalProcessError as e:
         raise PowerActionError(
             "Failed to power %s %s at %s: %s" %
             (power_change, server_id, ip, e.output_as_unicode))
Exemple #6
0
 def _set_outlet_state(self,
                       power_change,
                       outlet_id=None,
                       power_user=None,
                       power_pass=None,
                       power_address=None,
                       **extra):
     """Power DLI outlet ON/OFF."""
     try:
         url = "http://%s:%s@%s/outlet?%s=%s" % (
             power_user,
             power_pass,
             power_address,
             outlet_id,
             power_change,
         )
         # --auth-no-challenge: send Basic HTTP authentication
         # information without first waiting for the server's challenge.
         call_and_check(
             ["wget", "--auth-no-challenge", "-O", "/dev/null", url],
             env=get_env_with_locale(),
         )
     except ExternalProcessError as e:
         raise PowerActionError(
             "Failed to power %s outlet %s: %s" %
             (power_change, outlet_id, e.output_as_unicode))
Exemple #7
0
 def test_removes_hosts_from_leases_file(self):
     path = self.make_file(contents=LEASES_FILE_WITH_HOSTS)
     call_and_check([
         "%s/scripts/maas-dhcp-helper" % root,
         "clean",
         path,
     ])
     self.assertEquals(LEASES_FILE_WITHOUT_HOSTS, read_text_file(path))
Exemple #8
0
def running_in_container():
    """Return True if running in an LXC or Docker container."""
    try:
        call_and_check(["systemd-detect-virt", "-c"])
    except ExternalProcessError:
        # Exited non-zero so not in a container.
        return False
    else:
        # Exited zero so inside a container.
        return True
Exemple #9
0
    def test_sends_notification_over_socket_for_processing(self):
        action = "commit"
        mac = factory.make_mac_address()
        ip_family = "ipv4"
        ip = factory.make_ipv4_address()
        lease_time = random.randint(30, 1000)
        hostname = factory.make_name("host")

        socket_path, service, done = self.catch_packet_on_socket()
        service.startService()
        self.addCleanup(service.stopService)

        call_and_check(
            [
                "%s/scripts/maas-dhcp-helper" % root,
                "notify",
                "--action",
                action,
                "--mac",
                mac,
                "--ip-family",
                ip_family,
                "--ip",
                ip,
                "--lease-time",
                str(lease_time),
                "--hostname",
                hostname,
                "--socket",
                socket_path,
            ]
        )
        yield done.get(timeout=10)

        self.assertThat(
            done.value[0],
            MatchesDict(
                {
                    "action": Equals(action),
                    "mac": Equals(mac),
                    "ip_family": Equals(ip_family),
                    "ip": Equals(ip),
                    "timestamp": IsInstance(int),
                    "lease_time": Equals(lease_time),
                    "hostname": Equals(hostname),
                }
            ),
        )
Exemple #10
0
 def _issue_ipmitool_command(self,
                             power_change,
                             power_address=None,
                             power_user=None,
                             power_pass=None,
                             power_hwaddress=None,
                             **extra):
     """Issue ipmitool command for HP Moonshot cartridge."""
     command = ('ipmitool', '-I', 'lanplus', '-H', power_address, '-U',
                power_user, '-P', power_pass) + tuple(
                    power_hwaddress.split())
     if power_change == 'pxe':
         command += ('chassis', 'bootdev', 'pxe')
     else:
         command += ('power', power_change)
     try:
         stdout = call_and_check(command, env=select_c_utf8_locale())
         stdout = stdout.decode('utf-8')
     except ExternalProcessError as e:
         raise PowerActionError(
             "Failed to execute %s for cartridge %s at %s: %s" %
             (command, power_hwaddress, power_address, e.output_as_unicode))
     else:
         # Return output if power query
         if power_change == 'status':
             match = re.search(r'\b(on|off)\b$', stdout)
             return stdout if match is None else match.group(0)
Exemple #11
0
 def _issue_fence_cdu_command(self,
                              command,
                              power_address=None,
                              power_id=None,
                              power_user=None,
                              power_pass=None,
                              **extra):
     """Issue fence_cdu command for the given power change."""
     try:
         stdout = call_and_check([
             'fence_cdu', '-a', power_address, '-n', power_id, '-l',
             power_user, '-p', power_pass, '-o', command
         ],
                                 env=get_env_with_locale())
     except ExternalProcessError as e:
         # XXX 2016-01-08 newell-jensen, bug=1532310:
         # fence-agents fence_action method returns an exit code
         # of 2, by default, for querying power status while machine
         # is OFF.
         if e.returncode == 2 and command == 'status':
             return "Status: OFF\n"
         else:
             raise PowerError(
                 "Fence CDU failed issuing command %s for Power ID %s: %s" %
                 (command, power_id, e.output_as_unicode))
     else:
         return stdout.decode("utf-8")
Exemple #12
0
def generate_rndc(port=953,
                  key_name='rndc-maas-key',
                  include_default_controls=True):
    """Use `rndc-confgen` (from bind9utils) to generate a rndc+named
    configuration.

    `rndc-confgen` generates the rndc configuration which also contains, in
    the form of a comment, the 'named' configuration we need.
    """
    # Generate the configuration:
    # - 256 bits is the recommended size for the key nowadays.
    # - Use urandom to avoid blocking on the random generator.
    rndc_content = call_and_check([
        'rndc-confgen', '-b', '256', '-r', '/dev/urandom', '-k', key_name,
        '-p',
        str(port).encode("ascii")
    ])
    rndc_content = rndc_content.decode("ascii")
    named_comment = extract_suggested_named_conf(rndc_content)
    named_conf = uncomment_named_conf(named_comment)

    # The 'named' configuration contains a 'control' statement to enable
    # remote management by MAAS.  If appropriate, add one to enable remote
    # management by the init scripts as well.
    if include_default_controls:
        named_conf += DEFAULT_CONTROLS

    # Return a tuple of the two configurations.
    return rndc_content, named_conf
Exemple #13
0
def get_maas_branch_version():
    """Return the Bazaar revision for this running MAAS.

    :return: An integer if MAAS is running from a Bazaar working tree, else
        `None`. The revision number is only representative of the BRANCH, not
        the working tree.
    """
    try:
        revno = shell.call_and_check(("bzr", "revno", __file__))
    except shell.ExternalProcessError:
        # We may not be in a Bazaar working tree, or any manner of other
        # errors. For the purposes of this function we don't care; simply say
        # we don't know.
        return None
    except FileNotFoundError:
        # Bazaar is not installed. We don't care and simply say we don't know.
        return None
    else:
        # `bzr revno` can return '???' when it can't find the working tree's
        # current revision in the branch. Hopefully a fairly unlikely thing to
        # happen, but we guard against it, and other ills, here.
        try:
            return int(revno)
        except ValueError:
            return None
Exemple #14
0
    def _query_outlet_state(
        self,
        outlet_id=None,
        power_user=None,
        power_pass=None,
        power_address=None,
        **extra
    ):
        """Query DLI outlet power state.

        Sample snippet of query output from DLI:
        ...
        <!--
        function reg() {
        window.open('http://www.digital-loggers.com/reg.html?SN=LPC751740');
        }
        //-->
        </script>
        </head>
        <!-- state=02 lock=00 -->

        <body alink="#0000FF" vlink="#0000FF">
        <FONT FACE="Arial, Helvetica, Sans-Serif">
        ...
        """
        try:
            url = "http://%s:%s@%s/index.htm" % (
                power_user,
                power_pass,
                power_address,
            )
            # --auth-no-challenge: send Basic HTTP authentication
            # information without first waiting for the server's challenge.
            wget_output = call_and_check(
                ["wget", "--auth-no-challenge", "-qO-", url],
                env=get_env_with_locale(),
            )
            wget_output = wget_output.decode("utf-8")
            match = re.search("<!-- state=([0-9a-fA-F]+)", wget_output)
            if match is None:
                raise PowerError(
                    "Unable to extract power state for outlet %s from "
                    "wget output: %s" % (outlet_id, wget_output)
                )
            else:
                state = match.group(1)
                # state is a bitmap of the DLI's oulet states, where bit 0
                # corresponds to oulet 1's power state, bit 1 corresponds to
                # outlet 2's power state, etc., encoded as hexadecimal.
                if (int(state, 16) & (1 << int(outlet_id) - 1)) > 0:
                    return "on"
                else:
                    return "off"
        except ExternalProcessError as e:
            raise PowerActionError(
                "Failed to power query outlet %s: %s"
                % (outlet_id, e.output_as_unicode)
            )
Exemple #15
0
def call_dnssec_keygen(tmpdir):
    path = os.environ.get("PATH", "").split(os.pathsep)
    path.append("/usr/sbin")
    env = dict(os.environ, PATH=os.pathsep.join(path))
    return call_and_check([
        'dnssec-keygen', '-r', '/dev/urandom', '-a', 'HMAC-MD5', '-b', '512',
        '-n', 'HOST', '-K', tmpdir, '-q', 'omapi_key'
    ],
                          env=env)
Exemple #16
0
def get_ip_addr():
    """Returns this system's local IP address information as a dictionary.

    :raises:ExternalProcessError: if IP address information could not be
        gathered.
    """
    ip_addr_output = call_and_check(["ip", "addr"])
    ifaces = parse_ip_addr(ip_addr_output)
    return annotate_with_driver_information(ifaces)
Exemple #17
0
def get_ip_route():
    """Returns this system's local IP route information as a dictionary.

    :raises:ExternalProcessError: if IP route information could not be
        gathered.
    """
    ip_route_output = call_and_check(
        ["ip", "route", "list", "scope", "global"])
    return parse_ip_route(ip_route_output)
Exemple #18
0
def _git_cmd(*cmd):
    """Run a git command and return output, or None in case of error."""
    try:
        return (shell.call_and_check(["git"] +
                                     list(cmd)).decode("ascii").strip())
    except (shell.ExternalProcessError, FileNotFoundError):
        # We may not be in a git repository, or any manner of other errors, or
        # git is not installed.
        return None
Exemple #19
0
def get_architecture():
    """Get the architecture of the running system."""
    try:
        stdout = call_and_check('archdetect').decode('utf-8')
    except ExternalProcessError:
        return ''
    arch, subarch = stdout.strip().split('/')
    if arch in ['i386', 'amd64', 'arm64', 'ppc64el']:
        subarch = 'generic'
    return '%s/%s' % (arch, subarch)
Exemple #20
0
def get_architecture():
    """Get the architecture of the running system."""
    try:
        stdout = call_and_check("archdetect").decode("utf-8")
    except ExternalProcessError:
        return ""
    arch, subarch = stdout.strip().split("/")
    if arch in ["i386", "amd64", "arm64", "ppc64el"]:
        subarch = "generic"
    return "%s/%s" % (arch, subarch)
Exemple #21
0
def get_ip_addr():
    """Returns this system's local IP address information as a dictionary.

    :raises:ExternalProcessError: if IP address information could not be
        gathered.
    """
    output = call_and_check([get_resources_bin_path()])
    ifaces = parse_lxd_networks(json.loads(output)["networks"])
    _update_interface_type(ifaces)
    _annotate_with_proc_net_bonding_original_macs(ifaces)
    return ifaces
Exemple #22
0
def get_ip_route():
    """Returns this system's local IP route information as a dictionary.

    :raises:ExternalProcessError: if IP route information could not be
        gathered.
    """
    output = call_and_check(
        ["ip", "-json", "route", "list", "scope", "global"])
    routes = {}
    for entry in json.loads(output):
        routes[entry.pop("dst")] = entry
    return routes
Exemple #23
0
def call_uec2roottar(root_image_path, root_tgz_path):
    """Invoke `uec2roottar` with the given arguments.

    Here only so tests can stub it out.

    :param root_image_path: Input file.
    :param root_tgz_path: Output file.
    """
    if is_dev_environment():
        # In debug mode this is skipped as it requires the uec2roottar
        # script to have sudo abilities. The root-tgz is created as an
        # empty file so the correct links can be made.
        log.msg(
            "Conversion of root-image to root-tgz is skipped in DEVELOP mode.")
        open(root_tgz_path, "wb").close()
    else:
        call_and_check([
            'sudo', '/usr/bin/uec2roottar',
            '--user=maas',
            root_image_path,
            root_tgz_path,
            ])
Exemple #24
0
def get_maas_repo_hash():
    """Return the Git hash for this running MAAS.

    :return: A string if MAAS is running from a git working tree, else `None`.
    """
    try:
        return shell.call_and_check(['git', 'rev-parse',
                                     'HEAD']).decode('ascii').strip()
    except shell.ExternalProcessError:
        # We may not be in a git repository, or any manner of other errors. For
        # the purposes of this function we don't care; simply say we don't
        # know.
        return None
    except FileNotFoundError:
        # Git is not installed. We don't care and simply say we don't know.
        return None
Exemple #25
0
def find_ip_via_arp(mac: str) -> str:
    """Find the IP address for `mac` by reading the output of arp -n.

    Returns `None` if the MAC is not found.

    We do this because we aren't necessarily the only DHCP server on the
    network, so we can't check our own leases file and be guaranteed to find an
    IP that matches.

    :param mac: The mac address, e.g. '1c:6f:65:d5:56:98'.
    """
    output = call_and_check(['arp', '-n'])
    output = output.decode("ascii").splitlines()

    for line in sorted(output):
        columns = line.split()
        if len(columns) == 5 and columns[2].lower() == mac.lower():
            return columns[0]
    return None
Exemple #26
0
 def _issue_ipmitool_command(self,
                             power_change,
                             power_address=None,
                             power_user=None,
                             power_pass=None,
                             power_hwaddress=None,
                             **extra):
     """Issue ipmitool command for HP Moonshot cartridge."""
     command = (
         "ipmitool",
         "-I",
         "lanplus",
         "-H",
         power_address,
         "-U",
         power_user,
         "-P",
         power_pass,
         "-L",
         "OPERATOR",
     ) + tuple(power_hwaddress.split())
     if power_change == "pxe":
         command += ("chassis", "bootdev", "pxe")
     else:
         command += ("power", power_change)
     try:
         stdout = call_and_check(command, env=get_env_with_locale())
         stdout = stdout.decode("utf-8")
     except ExternalProcessError as e:
         raise PowerActionError(
             "Failed to execute %s for cartridge %s at %s: %s" % (
                 command,
                 power_hwaddress,
                 power_address,
                 e.output_as_unicode,
             ))
     else:
         # Return output if power query
         if power_change == "status":
             match = re.search(r"\b(on|off)\b$", stdout)
             return stdout if match is None else match.group(0)
Exemple #27
0
def find_mac_via_arp(ip: str) -> str:
    """Find the MAC address for `ip` by reading the output of arp -n.

    Returns `None` if the IP is not found.

    We do this because we aren't necessarily the only DHCP server on the
    network, so we can't check our own leases file and be guaranteed to find an
    IP that matches.

    :param ip: The ip address, e.g. '192.168.1.1'.
    """
    # Normalise ip.  IPv6 has a wealth of alternate notations, so we can't
    # just look for the string; we have to parse.
    ip = IPAddress(ip)
    # Use "C" locale; we're parsing output so we don't want any translations.
    output = call_and_check(['ip', 'neigh'], env={'LC_ALL': 'C'})
    output = output.decode("ascii").splitlines()

    for line in sorted(output):
        columns = line.split()
        if len(columns) < 4:
            raise Exception(
                "Output line from 'ip neigh' does not look like a neighbour "
                "entry: '%s'" % line)
        # Normal "ip neigh" output lines look like:
        #   <IP> dev <interface> lladdr <MAC> [router] <status>
        #
        # Where <IP> is an IPv4 or IPv6 address, <interface> is a network
        # interface name such as eth0, <MAC> is a MAC address, and status
        # can be REACHABLE, STALE, etc.
        #
        # However sometimes you'll also see lines like:
        #   <IP> dev <interface>  FAILED
        #
        # Note the missing lladdr entry.
        if IPAddress(columns[0]) == ip and columns[3] == 'lladdr':
            # Found matching IP address.  Return MAC.
            return columns[4]
    return None
Exemple #28
0
def call_dnssec_keygen(tmpdir):
    path = os.environ.get("PATH", "").split(os.pathsep)
    path.append("/usr/sbin")
    env = dict(os.environ, PATH=os.pathsep.join(path))
    return call_and_check(
        [
            "dnssec-keygen",
            "-r",
            "/dev/urandom",
            "-a",
            "HMAC-MD5",
            "-b",
            "512",
            "-n",
            "HOST",
            "-K",
            tmpdir,
            "-q",
            "omapi_key",
        ],
        env=env,
    )
Exemple #29
0
def run(args):
    """Register the rack controller with a region controller."""
    # If stdin supplied to program URL must be passed as argument.
    if not stdin.isatty() and args.url is None:
        print(
            "MAAS region controller URL must be given when supplying the "
            "shared secret via stdin with a non-interactive shell."
        )
        raise SystemExit(1)
    try:
        call_and_check(["systemctl", "stop", "maas-rackd"])
    except ExternalProcessError as e:
        print("Unable to stop maas-rackd service.", file=stderr)
        print("Failed with error: %s." % e.output_as_unicode, file=stderr)
        raise SystemExit(1)
    # maas_id could be stale so remove it
    set_maas_id(None)
    if args.url is not None:
        with ClusterConfiguration.open_for_update() as config:
            config.maas_url = args.url
    else:
        try:
            url = input("MAAS region controller URL: ")
        except EOFError:
            print()  # So that the shell prompt appears on the next line.
            raise SystemExit(1)
        except KeyboardInterrupt:
            print()  # So that the shell prompt appears on the next line.
            raise
        with ClusterConfiguration.open_for_update() as config:
            config.maas_url = url
        print("MAAS region controller URL saved as %s." % url)
    if args.secret is not None:
        set_shared_secret_on_filesystem(to_bin(args.secret))
    else:
        InstallSharedSecretScript.run(args)
    try:
        call_and_check(["systemctl", "enable", "maas-rackd"])
        call_and_check(["systemctl", "start", "maas-rackd"])
    except ExternalProcessError as e:
        print(
            "Unable to enable and start the maas-rackd service.", file=stderr
        )
        print("Failed with error: %s." % e.output_as_unicode, file=stderr)
        raise SystemExit(1)
Exemple #30
0
 def test_returns_standard_output(self):
     output = factory.make_string().encode("ascii")
     self.assertEqual(output, call_and_check(["/bin/echo", "-n", output]))