Exemple #1
0
 def test_release_force(self):
     t = ttbl.test_target("a")
     t.acquire(self.user1)
     t.release(self.user1, force = True)
     # This has to work now
     t.acquire(self.user2)
     t.release(self.user2)
Exemple #2
0
 def test_release_force(self):
     t = ttbl.test_target("a")
     t.acquire(self.user1)
     t.release(self.user1, force=True)
     # This has to work now
     t.acquire(self.user2)
     t.release(self.user2)
Exemple #3
0
 def test_release__bad_args(self):
     t = ttbl.test_target("a")
     with self.assertRaises(ttbl.test_target_not_acquired_e):
         t.release(self.usernil)
     t.acquire(self.user1)
     t.release(self.user1)
     with self.assertRaises(ttbl.test_target_not_acquired_e):
         t.release(self.user1)
Exemple #4
0
 def test_release__bad_args(self):
     t = ttbl.test_target("a")
     with self.assertRaises(ttbl.test_target_not_acquired_e):
         t.release(self.usernil)
     t.acquire(self.user1)
     t.release(self.user1)
     with self.assertRaises(ttbl.test_target_not_acquired_e):
         t.release(self.user1)
Exemple #5
0
 def test_acquire__bad_args(self):
     target = ttbl.test_target("a")
     with self.assertRaises(TypeError):
         target.acquire()  # pylint: disable = no-value-for-parameter
     with self.assertRaises(TypeError):
         target.acquire(None)
     with self.assertRaises(TypeError):
         target.acquire(3)
     with self.assertRaises(TypeError):
         target.acquire("a", 3)  # pylint: disable = too-many-function-args
     with self.assertRaises(TypeError):
         target.release()  # pylint: disable = no-value-for-parameter
     with self.assertRaises(TypeError):
         target.release(None)
     with self.assertRaises(TypeError):
         target.release(3)
     with self.assertRaises(TypeError):
         target.release("a", 3)  # pylint: disable = too-many-function-args
Exemple #6
0
 def test_acquire__bad_args(self):
     target = ttbl.test_target("a")
     with self.assertRaises(TypeError):
         target.acquire()	# pylint: disable = no-value-for-parameter
     with self.assertRaises(TypeError):
         target.acquire(None)
     with self.assertRaises(TypeError):
         target.acquire(3)
     with self.assertRaises(TypeError):
         target.acquire("a", 3) # pylint: disable = too-many-function-args
     with self.assertRaises(TypeError):
         target.release()	# pylint: disable = no-value-for-parameter
     with self.assertRaises(TypeError):
         target.release(None)
     with self.assertRaises(TypeError):
         target.release(3)
     with self.assertRaises(TypeError):
         target.release("a", 3) # pylint: disable = too-many-function-args
Exemple #7
0
 def test_acquire__busy(self):
     t = ttbl.test_target("a")
     t.acquire(self.user1)
     with self.assertRaises(ttbl.test_target_busy_e):
         t.acquire(self.user2)
Exemple #8
0
 def test_acquire__good(self):
     target = ttbl.test_target("d")
     who = self.user1
     target.acquire(who)
     self.assertEqual(who, target.owner_get())
     target.release(who)
Exemple #9
0
        x, y, _vlan_id = nw_indexes(letter)
        nw_name = "nw" + letter

        # Add the network target
        nw_pos_add(letter)

        # Add QEMU UEFI POS capable targets with addresses
        v = 1
        for v in range(
                ttbl.config.default_qemu_start,
                ttbl.config.default_qemu_start
                + ttbl.config.default_qemu_count):
            target_name = "qu-%02d" % v + letter
            target = target_qemu_pos_add(
                target_name,
                nw_name,
                mac_addr = "02:%02x:00:00:%02x:%02x" % (x, y, v),
                ipv4_addr = '192.%d.%d.%d' % (x, y, v),
                ipv6_addr = 'fd:%02x:%02x::%02x' % (x, y, v))

    index_start = 5

ttbl.config.target_add(
    ttbl.test_target('local'),
    tags = {
        "versions.server": commonl.version_get(ttbl, "ttbd"),
        "skip_cleanup": True,
        "disabled": "meant only for describing the server",
    }
)
Exemple #10
0
def nw_pos_add(nw_name,
               power_rail=None,
               mac_addr=None,
               vlan=None,
               ipv4_prefix_len=24,
               ipv6_prefix_len=104):
    """Adds configuration for a network with :ref:`Provisioning OS
    <provisioning_os>` support.

    This setups the network with the power rails needed for targets
    that can boot Provisioning OS to deploy images to their hard
    drives.

    For example, to add *nwb*, *192.168.98.0/24* with the server on
    *192.168.98.1* adding proxy port redirection from the isolated network
    to that upstream the server:

    >>> x, y, _ = nw_indexes('b')
    >>> interconnect = nw_pos_add(
    >>>     'b', mac_addr = '00:50:b6:27:4b:77',
    >>>     power_rail = [
    >>>         ttbl.pc.dlwps7('http://*****:*****@sp7/4'),
    >>>         # disable the proxy redirection, using tinyproxy
    >>>         # running on :8888
    >>>         # Mirrors of Clear and other stuff, see distro_mirrors below
    >>>         ttbl.socat.pci('tcp', "192.%d.%d.1" % (x, y), 1080,
    >>>                        'linux-ftp.jf.intel.com', 80),
    >>>         ttbl.socat.pci('tcp', "192.%d.%d.1" % (x, y), 1443,
    >>>                        'linux-ftp.jf.intel.com', 443),
    >>>     ])
    >>>
    >>> interconnect.tags_update(dict(
    >>>     # implemented by tinyproxy running in the server
    >>>     ftp_proxy = "http://192.%d.%d.1:8888" % (x, y),
    >>>     http_proxy = "http://192.%d.%d.1:8888" % (x, y),
    >>>     https_proxy = "http://192.%d.%d.1:8888" % (x, y),

    Note how first we calculate, from the network names the nibbles
    we'll use for IP addresses. This is only needed because we are
    adding extras to the basic configuration.

    :param str nw_name: network name, which must be one or two ASCII
      letters, uppwer or lowercase; see :ref:`best naming practices
      <bp_naming_networks>`.

      >>> letter = "aD"

      would yield a network called *nwAD*.

    :param str mac_addr: (optional) if specified, this is connected to
      the physical network adapter in the server with the given MAC
      address in six 16-bit hex digits (*hh:hh:hh:hh:hh:hh*).

      Note the TCF server will take over said interface, bring it up,
      down, remove and add IP address etc, so it cannot be shared with
      any interface being used for other things.

    :param int vlan: (optional) use Ethernet VLANs

      - None: do not use vlans (default)
      - 0: configure this network to use a VLAN on the physical
        interface; the VLAN ID is calculated from the network
        name.
      - N > 0: the number is used as the VLAN ID.

    :param list power_rail: (optional) list of
      :class:`ttbl.power.impl_c` objects that control power
      to this network.

      This can be used to power on/off switches, start daemons, etc
      when the network is started:

      >>> power_rail = [
      >>>     # power on the network switch plugged to PDU sp7, socket 4
      >>>     ttbl.pc.dlwps7('http://*****:*****@sp7/4'),
      >>>     # start two port redirectors to a proxy
      >>>     ttbl.socat.pci('tcp', "192.168.%d.1" % nw_idx, 1080,
      >>>                    'proxy-host.domain', 80),
      >>>     ttbl.socat.pci('tcp', "192.168.%d.1" % nw_idx, 1443,
      >>>                    'proxy-host.domain', 443),
      >>> ]

    :returns: the interconect object added
    """
    assert vlan == None or vlan >= 0
    assert vlan == None or isinstance(mac_addr, str)

    if power_rail == None:
        power_rail = []
    # we don't check the power_rail, since ttb.power.interface() will
    # do for us.

    x, y, _vlan_id = nw_indexes(nw_name)
    nw_name = "nw" + nw_name

    # Create the network target
    interconnect = ttbl.test_target(nw_name)

    interconnect.interface_add(
        "power",
        ttbl.power.interface(
            *  # yeah, asterisk, so this is converted to *args...
            # Virtual networking inside the server, for the VMs
            [("vlan setup", vlan_pci())]
            # Power rails passed by the user, to power on switches or whatever
            + power_rail
            # the rest of the components we need
            + [
                ("rsync",
                 ttbl.rsync.pci("192.%d.%d.1" %
                                (x, y), 'images', '/home/ttbd/images')),
                ("dnsmasq", ttbl.dnsmasq.pc()),
            ]))

    # Consoles to read misc log files
    interconnect.interface_add(
        "console",
        ttbl.console.interface(
            **{"log-dnsmasq": ttbl.console.logfile_c("dnsmasq.log")}))

    tags = dict(
        ipv6_addr='fd:%02x:%02x::1' % (x, y),
        ipv6_prefix_len=ipv6_prefix_len,
        ipv4_addr='192.%d.%d.1' % (x, y),
        ipv4_prefix_len=ipv4_prefix_len,
        # implemented by tinyproxy running in the server
        ftp_proxy="http://192.%d.%d.1:8888" % (x, y),
        http_proxy="http://192.%d.%d.1:8888" % (x, y),
        https_proxy="http://192.%d.%d.1:8888" % (x, y),
        # Provisioning OS support to boot off PXE on nfs root
        pos_http_url_prefix="http://192.%d.%d.1/ttbd-pos/%%(bsp)s/" % (x, y),
        # FIXME: have the daemon hide the internal path?
        pos_nfs_server="192.%d.%d.1" % (x, y),
        pos_nfs_path="/home/ttbd/images/tcf-live/%(bsp)s",
        # implemented by ttbl.rsync above
        pos_rsync_server="192.%d.%d.1::images" % (x, y))

    if mac_addr:
        tags['mac_addr'] = mac_addr
    if vlan == None:
        pass
    elif vlan == 0:
        tags['vlan'] = _vlan_id
    elif vlan > 0:
        tags['vlan'] = vlan

    ttbl.config.interconnect_add(
        interconnect,
        tags=tags,  # add it
        ic_type="ethernet")
    return interconnect
Exemple #11
0
 def test_acquire__good(self):
     target = ttbl.test_target("d")
     who = self.user1
     target.acquire(who)
     self.assertEqual(who, target.owner_get())
     target.release(who)
Exemple #12
0
def pos_target_add(
        name,  # TTYPE-INDEX
        mac_addr,  # HH:HH:HH:HH:HH:HH
        power_rail,  # spX/N
        boot_disk,  # "sda",
        pos_partsizes,  # "1:20:50:35",
        linux_serial_console,  # 'ttyUSB0'
        target_type=None,  # eg "NUC5i",
        target_type_long=None,  # eg "Intel NUC5i5425OU",
        index=None,  # 3, 4, ... formatted as %02
        network=None,  # 'a', "AB", etc...
        power_on_pre_hook=None,
        extra_tags=None,
        pos_nfs_server=None,
        pos_nfs_path=None,
        pos_rsync_server=None,
        boot_config=None,
        boot_config_fix=None,
        boot_to_normal=None,
        boot_to_pos=None,
        mount_fs=None,
        pos_http_url_prefix=None,
        pos_image=None,
        ipv4_prefix_len=24,
        ipv6_prefix_len=104,
        serial_console_name="serial0",
        tags=None):
    """
    Add a PC-class target that can be provisioned using Provisioning
    OS.

    :param str name: target's name, following the convention
      :ref:`*TYPE-NNNETWORK* <bp_naming_targets>`:

      - *TYPE* is the target's short type that describes targets that
        are generally the same
      - *NN* is a number 2 to 255
      - *NETWORK* is the name of the network it is connected to (the
        network target is actuall called *nwNETWORK*), see :ref:`naming
        *networks <bp_naming_networks>`.

      >>> pos_target_add('nuc5-02a', ..., target_type = "Intel NUC5i5U324")

    :param str mac_addr: MAC address for this target on its connection
      to network *nwNETWORK*.

      Can't be the same as any other MAC address in the system or that
      network. It shall be in the standard format of six hex digits
      separated by colons:

      >>> pos_target_add('nuc5-02a', 'c0:3f:d5:67:07:81', ...)

    :param ttbl.power.impl_c power_rail: Power control instance
      to power switch this target, eg:

      >>> pos_target_add('nuc5-02a', 'c0:3f:d5:67:07:81',
      >>>                ttbl.pc.dlwps7("http://*****:*****@POWERSWITCHANEM/3"),
      >>>                ...)

      This can also be a list of these if multiple components need to
      be powered on/off to power on/off the target.

      >>> pos_target_add('nuc5-02a', 'c0:3f:d5:67:07:81',
      >>>                [
      >>>                    ttbl.pc.dlwps7("http://*****:*****@POWERSWITCHANEM/3"),
      >>>                    ttbl.pc.dlwps7("http://*****:*****@POWERSWITCHANEM/4"),
      >>>                    ttbl.ipmi.pci("BMC_HOSTNAME")
      >>>                ],
      >>>                ...)
      >>>

    :param str power_rail: Address of the :class:`Digital Logger Web
      Power Switch <ttbl.pc.dlwps7>` in the form
      *[USER:PASSWORD@]HOSTNAME/PLUGNUMBER*.

      **LEGACY**

      eg: for a target *nuc5-02a* connected to plug #5 of a DLWPS7 PDU
      named *sp10*

      >>> pos_target_add('nuc5-02a', power_rail_dlwps = 'sp10/5', ...)

      Note there has to be at least one power spec given

    :param str boot_disk: base name of the disk (as seen by Linux)
      from which the device will boot to configure it as a boot loader
      and install a root filesystem on it

      eg for *nuc5-02a*:

      >>> pos_target_add("nuc5-2a", MAC, POWER, 'sda')

      Note */dev/* is not needed.

    :param str pos_partsizes: sizes of the partitions to create; this is a
      list of four numbers with sizes in gigabytes for the boot, swap,
      scratch and root partitions.

      eg:

      >>> pos_target_add("nuc5-2a", ..., pos_partsizes = "1:4:10:5")

      will create in this target a boot partition 1 GiB in size, then
      a swap partition 4 GiB, a scratch partition 10 GiB and then
      multiple root filesystem partitons of 5 GiB each (until the disk
      is exhausted).

    :param str linux_serial_console: name of the device that Linux
      sees when it boots as a serial console

      eg:

      >>> pos_target_add("nuc5-02a", ... linux_serial_console = "ttyS0"...)
      >>> pos_target_add("nuc6-03b", ... linux_serial_console = "ttyUSB0"...)

      Note */dev/* is not needed and that this is the device the
      **target** sees, not the server.

    :param str target_type: (optional) override target's type (guessed
      from the name), which will be reported in the *type* target
      metadata; eg, for *Intel NUC5i5*:

      >>> pos_target_add("nuc5-02a", ..., target_type = "Intel NUC5i5U324")

      The HW usually has many different types that are extremely
      similar; when such is the case, the *type* can be set to a
      common prefix and the tag *type_long* then added to contain the
      full type name (this helps simplifying the setup); see
      *target_type_long* and *extra_tags* below.

    :param str target_type_long: (optional) long version of the target
      type (see above). Defaults to the same as *target_type*

    :param int index: (optional) override the target's index guessed
      from the name with a (between 2 and 254); in the name it will be
      formatted with at least two decimal digits.

      >>> pos_target_add("nuc5-02a", index = 3, ...)

      In this case, trget *nuc-02a* will be assigned a default IP
      address of 192.168.97.3 instead of 192.168.97.2.

    :param str network: (optional) override the network name guessed
      from the target's name.

      This is one or two ASCII letters, uppwer or lowercase; see
      :ref:`best naming practices <bp_naming_networks>`.

      eg for *nuc5-02c*:

      >>> pos_target_add("nuc5-02c", network = 'a', ...)

      The network naming convention *nwa* of the example help keep
      network names short, needed for internal interface name
      limitation in Linux (for example). Note the IP addresses for nwX
      are *192.168.ascii(X).0/24*; thus for *nuc5-02a* in the example,
      it's IP address will be *192.168.168.65.2*.

      If the network were, for example, *Gk*, the IP address would be
      *192.71.107.2* (71 being ASCII(G), 107 ASCII(k)).

    :param dict extra_tags: extra tags to add to the target for information

      eg:

      >>> pos_target_add(name_prefix = "nuc5", ..., dict(
      >>>    fixture_usb_disk = "4289273ADF334",
      >>>    fixture_usb_disk = "4289273ADF334"
      >>> ))

    :param power_on_pre_hook: (optional) function the server
      calls before powering on the target so so it boots Provisioning
      OS mode or normal mode.

      This might be configuring the DHCP server to offer a TFTP file
      or configuring the TFTP configuration file a bootloader will
      pick, etc; for examples, look at:

      - :func:`ttbl.dhcp.power_on_pre_pos_setup`
      - :meth:`ttbl.ipmi.pci.pre_power_pos_setup`

      Default is :func:`ttbl.dhcp.power_on_pre_pos_setup`.

    For other parameters possible to control the POS settings, please
    look at :func:`target_pos_setup`

    """

    assert isinstance(name, str), \
        "name must be a string; got: %s %s" % (type(name).__name__, name)
    assert isinstance(mac_addr, str), \
        "mac_addr must be a string HH:HH:HH:HH:HH:HH; got: %s %s" \
        % (type(name).__name__, name)
    if power_rail:
        # FIXME: move to ttlb.power.validate_spec
        if isinstance(power_rail, ttbl.power.impl_c):
            pass
        elif isinstance(power_rail, str):
            # compat, a descriptor that gets transformed to a pc_dlwps7
            pass
        elif isinstance(power_rail, list):
            count = -1
            for pc in power_rail:
                count += 1
                if isinstance(pc, ttbl.power.impl_c):
                    continue
                if isinstance(pc, (tuple, list)):
                    # list of NAME, IMPL
                    if len(pc) != 2:
                        raise AssertionError(
                            "power rail #%d: list of %d items given;"
                            " expect 2 (NAME, IMPL)" % (count, len(pc)))
                    pc_name = pc[0]
                    pc_impl = pc[1]
                    if not isinstance(pc_name, str):
                        raise AssertionError(
                            "power rail #%d: first element must be a string; got %s"
                            % (count, type(pc_name)))
                    if not isinstance(pc_impl, ttbl.power.impl_c):
                        raise AssertionError(
                            "power rail #%d: second element must be a ttbl.power.impl_c; got %s"
                            % (count, type(pc_impl)))
        else:
            raise AssertionError(
                "power rail must be a power rail spec (ttbl.power.impl_c"
                " or list of them), see doc; got %s" % power_rail)
    assert isinstance(boot_disk, str) \
        and not '/' in boot_disk, \
        'boot_disk is the base name of the disk from which ' \
        'the device boots, eg "sda"; got: %s' % boot_disk
    assert isinstance(pos_partsizes, str) \
        and _partsizes_regex.search(pos_partsizes), \
        "pos_partsizes must match %s; got: %s" \
        % (_partsizes_regex.pattern, pos_partsizes)
    assert isinstance(linux_serial_console, str) \
        and _linux_serial_console_regex.search(linux_serial_console), \
        "linux_serial_console must be astring matching %s; got: %s" \
        % (_linux_serial_console_regex.pattern, linux_serial_console)

    assert target_type == None \
        or isinstance(target_type, str) \
        and _target_type_regex.match(target_type), \
        "target_type must match %s; got %s" \
        % (_target_type_regex.pattern, target_type)
    assert target_type_long == None \
        or isinstance(target_type_long, str) \
        and _target_type_long_regex.match(target_type_long), \
        "target_type_long must match %s; got %s" \
        % (_target_type_long_regex.pattern, target_type_long)
    assert index == None or index >= 2 and index < 255, \
        "target index has to be between 2 and 255; got %d" % index
    assert network == None or isinstance(network, str), \
        "network has to be a string; got %s" % network
    # nw_indexes() does the real checks
    # FIXME: tag verification? done by target_add, but we need a
    # check in commonl that can work for both client and server
    assert extra_tags == None or isinstance(extra_tags, dict), \
        "extra_tags has to be a dictionary of tags; got %s" % network

    if not power_on_pre_hook:
        power_on_pre_hook = ttbl.dhcp.power_on_pre_pos_setup
    else:
        assert callable(power_on_pre_hook)

    # from the name given, following convention TYPE-NNNETWORK, guess
    # the target type, target index and network name.
    _target_type, _index, _network = pos_target_name_split(name)
    # do we override them from the arguments?
    if target_type == None:
        target_type = _target_type
    if index == None:
        index = _index
        assert index >= 2 and index < 255, \
            "target index has to be between 2 and 255; got %d" % index
    if network == None:
        network = _network
    # FIXME: allow this to come from arguments?
    nw_name = "nw" + network
    x, y, _ = nw_indexes(network)

    target = ttbl.test_target(name)  # create the target object
    if tags == None:
        tags = {    # bake in base tags
            'linux': True,
            'bsp_models': { 'x86_64': None },
            'bsps': {
                'x86_64': {
                    'linux': True,
                    'console': 'x86_64',
                }
            },
        }
    else:
        assert isinstance(tags, dict)
    if target_type_long:
        tags['type_long'] = target_type_long
    else:
        tags['type_long'] = target_type
    if extra_tags:  # add/modify tags?
        tags.update(extra_tags)

    # Consoles
    #
    # serial_pc defaults to open /dev/tty-TARGETNAME; we need to
    # create the object because we'll need to start it upon power on
    pcl = []
    consoles = dict(ssh0=ttbl.console.ssh_pc("root@" + '192.%d.%d.%d' %
                                             (x, y, index)),
                    default="ssh0")
    if serial_console_name:
        serial_console = ttbl.console.serial_pc()
        consoles[serial_console_name] = serial_console
        consoles['default'] = serial_console_name
        pcl.append((serial_console_name, serial_console))
    target.interface_add("console", ttbl.console.interface(**consoles))

    # Power Rail
    #
    if isinstance(power_rail, str):
        # legacy support for URLs for dlwps7
        # remove user/pasword
        _name = re.sub(r"^([^\s]+:)?([^\s]+@)?", "", power_rail)
        if "@" in power_rail:  # use given user password
            pcl.append(("AC", ttbl.pc.dlwps7("http://%s" % power_rail)))
        else:  # use default password
            pcl.append(
                ("AC", ttbl.pc.dlwps7("http://*****:*****@%s" % power_rail)))
    elif isinstance(power_rail, ttbl.power.impl_c):
        # already asserted above
        pcl.append(power_rail)
    elif isinstance(power_rail, list):
        # FIXME: do this in tuple
        # already asserted above
        pcl = power_rail
    else:
        raise AssertionError()  # we checked we'd never get here anyway
    target.interface_add("power", ttbl.power.interface(*pcl))

    # POS support
    #
    target_pos_setup(target,
                     nw_name,
                     boot_disk,
                     linux_serial_console,
                     pos_nfs_server=pos_nfs_server,
                     pos_nfs_path=pos_nfs_path,
                     pos_rsync_server=pos_rsync_server,
                     boot_config=boot_config,
                     boot_config_fix=boot_config_fix,
                     boot_to_normal=boot_to_normal,
                     boot_to_pos=boot_to_pos,
                     mount_fs=mount_fs,
                     pos_http_url_prefix=pos_http_url_prefix,
                     pos_image=pos_image,
                     pos_partsizes=pos_partsizes)
    # hook up PXE/POS control before powering on
    target.power_on_pre_fns.append(power_on_pre_hook)

    # Add the target to the system
    ttbl.config.target_add(target, tags=tags, target_type=target_type)
    target.add_to_interconnect(  # Add target to the interconnect
        nw_name,
        dict(mac_addr=mac_addr,
             ipv4_addr='192.%d.%d.%d' % (x, y, index),
             ipv4_prefix_len=ipv4_prefix_len,
             ipv6_addr='fd:%02x:%02x::%02x' % (x, y, index),
             ipv6_prefix_len=ipv6_prefix_len))
    return target
Exemple #13
0
    def _dhcp_conf_write(self, f):
        kws = dict(self._params)
        # generate DHCP configuration file based on hackish templating
        self.log.info(
            "%(if_name)s: IPv%(ip_mode)d addr/net/mask "
            "%(if_addr)s/%(if_net)s/%(if_len)s", self._params)
        if self.ip_mode == 4:
            # We only do PXE over ipv4
            # FIXME: make it so using pxelinux is a configuratio template
            # (likewise on the tftp side, so we can switch to EFI boot or
            # whatever we want)
            # %(dhcp_architecture_types)s is the output of
            # _mk_pxe_arch_type_config()
            f.write("""\
option space pxelinux;
option pxelinux.magic code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;
# To be used in the pxeclients class
option architecture-type code 93 = unsigned integer 16;

subnet %(if_net)s netmask %(if_netmask)s {
        pool {
                %(allow_known_clients)s
                range %(ip_addr_range_bottom)s  %(ip_addr_range_top)s;
        }
        class "pxeclients" {
                match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
                # http://www.syslinux.org/wiki/index.php?title=PXELINUX#UEFI
%(dhcp_architecture_types)s
                # Point to the TFTP server, which is the same as this
                next-server %(if_addr)s;
        }
}
""" % self._params)
        else:
            f.write("""\
# This one line must be outside any bracketed scope
option architecture-type code 93 = unsigned integer 16;

subnet6 %(if_net)s/%(if_len)s {
        range6 %(ip_addr_range_bottom)s %(ip_addr_range_top)s;

        class "pxeclients" {
                match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
                # http://www.syslinux.org/wiki/index.php?title=PXELINUX#UEFI
%(dhcp_architecture_types)s
                # Point to the TFTP server, which is the same as this
#                next-server %(if_addr)s;
        }
}
""" % self._params)

        # Now, enumerate the targets that are in this local
        # configuration and figure out what's their IP address in
        # this network; create a hardcoded entry for them.
        #
        # FIXME: This leaves a gap, as targets in other servers could
        # be connected to this network. Sigh.
        for target in ttbl.test_target.known_targets():
            interconnects = target.tags.get('interconnects', {})
            ic = self.target

            boot_ic = target.tags.get('pos_boot_interconnect', None)
            if boot_ic == None:
                ic.log.info('%s: target has no "pos_boot_interconnect" '
                            'tag/property defined, ignoring' % target.id)
                continue
            # FIXME: these two checks shall be consistency done when
            # the target is being added
            if not boot_ic in target.tags['interconnects']:
                raise RuntimeError('%s: target does not belong to the '
                                   'boot interconnect "%s" defined in tag '
                                   '"pos_boot_interconnect"' %
                                   (target.id, boot_ic))
            boot_ic_target = ttbl.test_target(boot_ic)
            if boot_ic_target == None:
                raise RuntimeError('%s: this target\'s boot interconnect %s '
                                   'defined in "pos_boot_interconnect" tag '
                                   'is not available in this server' %
                                   (target.id, boot_ic))

            if not 'bsp' in target.tags:
                bsps = target.tags.get('bsps', {}).keys()
                if bsps:
                    kws['bsp'] = sorted(bsps)[0]
            kws.update(
                dict(
                    ipv4_gateway=ic.tags.get('ipv4_gateway', ""),
                    ipv4_netmask=commonl.ipv4_len_to_netmask_ascii(
                        ic.tags['ipv4_prefix_len']),
                    name=target.id,
                ))

            # There might be a prefix to the path to the boot kernel and
            # initrd; we let the target override it and default to the
            # network's or nothing
            # FIXME: need v6 nfs_server and http_url
            _tag_get_from_ic_target(kws, 'pos_http_url_prefix', ic, target)
            _tag_get_from_ic_target(kws, 'pos_nfs_server', ic, target)
            _tag_get_from_ic_target(kws, 'pos_nfs_path', ic, target)

            for ic_id, interconnect in interconnects.iteritems():
                if '#' in ic_id:
                    real_ic_id, instance = ic_id.split("#", 1)
                    kws['hostname'] = target.id + "-" + instance
                else:
                    real_ic_id = ic_id
                    kws['hostname'] = target.id
                if real_ic_id != self.target.id:
                    continue
                kws['mac_addr'] = interconnect.get('mac_addr', None)
                kws['ipv4_addr'] = interconnect.get('ipv4_addr', None)
                kws['ipv6_addr'] = interconnect.get('ipv6_addr', None)

                if self.ip_mode == 4:
                    config = """\
host %(hostname)s {
        hardware ethernet %(mac_addr)s;
        fixed-address %(ipv4_addr)s;
        option host-name "%(hostname)s";
        # note how we are forcing NFSv3, as it might default to v2
        # FIXME: parameter?
        # Also UDP, more resilient for our use and soft so we can
        # recover in some cases more easily
        option root-path "%(pos_nfs_server)s:%(pos_nfs_path)s,udp,soft,nfsvers=3";
}
"""
                else:
                    config = """\
host %(hostname)s {
        hardware ethernet %(mac_addr)s;
        fixed-address6 %(ipv6_addr)s;
        option host-name "%(hostname)s";
        # note how we are forcing NFSv3, as it might default to v2
        # FIXME: parameter?
        # Also UDP, more resilient for our use and soft so we can
        # recover in some cases more easily
        # FIXME: pos_nfs_server6?
        option root-path "%(pos_nfs_server)s:%(pos_nfs_path)s,udp,soft,nfsvers=3";
}
"""
                f.write(template_rexpand(config, kws))
Exemple #14
0
    for letter in ttbl.config.default_networks:
        x, y, _vlan_id = nw_indexes(letter)
        nw_name = "nw" + letter

        # Add the network target
        nw_pos_add(letter)

        # Add QEMU UEFI POS capable targets with addresses
        v = 1
        for v in range(
                ttbl.config.default_qemu_start,
                ttbl.config.default_qemu_start +
                ttbl.config.default_qemu_count):
            target_name = "qu-%02d" % v + letter
            target = target_qemu_pos_add(
                target_name,
                nw_name,
                mac_addr="02:%02x:00:00:%02x:%02x" % (x, y, v),
                ipv4_addr='192.%d.%d.%d' % (x, y, v),
                ipv6_addr='fc00::%02x:%02x:%02x' % (x, y, v))

    index_start = 5

ttbl.config.target_add(ttbl.test_target('local'),
                       tags={
                           "versions.server":
                           commonl.version_get(ttbl, "ttbd"),
                           "skip_cleanup": True,
                           "disabled": "meant only for describing the server",
                       })
Exemple #15
0
 def test_acquire__busy(self):
     t = ttbl.test_target("a")
     t.acquire(self.user1)
     with self.assertRaises(ttbl.test_target_busy_e):
         t.acquire(self.user2)
import ttbl
import ttbl.console

class console_loopback_c(ttbl.console.generic_c):
    def enable(self, target, component):
        write_file_name = os.path.join(target.state_dir,
                                      "console-%s.write" % component)
        # ensure it exists
        with open(write_file_name, "w") as wf:
            wf.write("")

        # now symlink the read to the write file, so what we write is
        # read right away 
        os.symlink(
            write_file_name,
            os.path.join(target.state_dir, "console-%s.read" % component),
        )
    
        ttbl.console.generic_c.enable(self, target, component)

target = ttbl.test_target("t0")
ttbl.config.target_add(target)
console_loopback = console_loopback_c()
target.interface_add("console", ttbl.console.interface(
    c1 = console_loopback,
    c2 = console_loopback,
    c3 = console_loopback,
    c4 = console_loopback,
))
Exemple #17
0
def target_qemu_pos_add(
        target_name,
        nw_name,
        mac_addr,
        ipv4_addr,
        ipv6_addr,
        consoles=None,
        disk_size="30G",
        mr_partsizes="1:2:2:10",
        # more distros support ide than virtio/scsi with
        # generic kernels
        sd_iftype='ide',
        extra_cmdline="",
        ram_megs=3072):
    """Add a QEMU virtual machine capable of booting over Provisioning OS.

    This target supports one or more serial consoles, a graphics
    interface exported via VNC and a single hard drive using
    :class:`ttbl.qemu.pc` as backend.

    Note this target uses a UEFI bios *and* defines UEFI storage
    space; this is needed so the right boot order is maintained.

    Add to a server configuration file ``/etc/ttbd-*/conf_*.py``

    >>> nw_pos_add("nwa")
    >>> target = target_qemu_pos_add("qu-05a"
    >>>                              "nwa",
    >>>                              mac_addr = "02:61:00:00:00:05",
    >>>                              ipv4_addr = "192.168.95.5",
    >>>                              ipv6_addr = "fd:00:61::05")

    See an example usage in :func:`conf_06_default.nw_default_targets_add`
    to create default targets.

    Extra paramenters can be added by using the *extra_cmdline*
    arguments, such as for example, to add another drive:

    >>> extra_cmdline = "-drive file=%%(path)s/hd-extra.qcow2,if=virtio,aio=threads"

    Adding to other networks:

    >>> target.add_to_interconnect(
    >>>     'nwb', dict(
    >>>         mac_addr = "02:62:00:00:00:05",
    >>>         ipv4_addr = "192.168.98.5",
    >>>         ipv6_addr = "fd:00:62::05")


    :param str target_name: name of the target to create

    :param str nw_name: name of the network to which this target will
      be connected that provides Provisioning OS services.

    :param str mac_addr: MAC address for this target (for QEMU usually
      a fake one).

      Will be given to the virtual device created and can't be the
      same as any other MAC address in the system or the networks. It
      is recommended to be in the format:

      >>> 02:HX:00:00:00:HY

      where *HX* and *HY* are two hex digits; *02:...* is a valid ethernet
      space. To simplify, it is recommended *HX* is the same for all
      the machines connected to the same network and *HY* matches some
      index in the target's name (eg *04* if target is called *q04*).

    :param str ipv4_addr: IPv4 Address (32bits, *DDD.DDD.DDD.DDD*, where
      *DDD* are decimal integers 0-255) that will be assigned to this
      target in the network.

    :param str ipv6_addr: IPv6 Address (128bits, standard ipv6 colon
      format) that will be assigned to this target in the network.

    :param list(str) consoles: (optional) names of serial consoles to
      create (defaults to just one, *ttyS0*). E.g:

      >>> consoles = [ "ttyS0", "ttyS1", "com3" ]

      these names are used to create the internal QEMU name and how
      the TCF daemon will refer to them as console names. In the
      machine, they will be standard serial ports in that order.

    :param str disk_size: (optional) size specification for the
      target's hard drive, as understood by QEMU's qemu-img create
      program. Defaults to:

      >>> disk_size = "30G"

    :param str mr_partsizes: (optional) sizes of the different
      partitions when using the :ref:`multiroot <pos_multiroot>`
      system to manage the target's disk; see
      :ref:`pos_multiroot_partsizes`.

      e.g.: *"1:10:30:20"* (default)

    :param str sd_iftype: (optional) interface to use for the disks
      (defaults to *ide* as is the one most Linux distro support off
      the bat). Available types are (per QEMU -drive option: ide,
      scsi, sd, mtd, floppy, pflash, virtio)

    :param int ram_megs: (optional) size of memory in megabytes
      (defaults to 2048)

    :param str extra_cmdline: a string with extra command line to add;
      %(FIELD)s supported (target tags).

    **Notes**

    - The hardrive gets fully reinitialized every time the server is
      restarted (the backend file gets wiped and re-created).

      It is still possible to force a re-partitioning of the backend
      by setting POS property :ref:`pos_reinitialize <pos_reinitialize>`.

    """
    assert isinstance(target_name, str)
    if consoles == None or consoles == []:
        consoles = ['ttyS0']
    else:
        assert isinstance(consoles, list) \
            and all([ isinstance(console, str)
                      for console in consoles ])
        assert len(consoles) >= 1
    assert ram_megs > 0
    # rest of parameters are checked by other functions we call

    target = ttbl.test_target(target_name)  # create the target object

    # Figure out the HD type
    pos_boot_dev = qemu_iftype_to_pos_boot_dev.get(sd_iftype, None)
    if pos_boot_dev == None:
        raise AssertionError("Don't know dev name for disk iftype %s" %
                             sd_iftype)

    # Create an HD for this guy -- we do it after creating the
    # target so the state path is created -- double check if the
    # drive already exists so not to override it? nah, screw
    # it--it is supposed to be all throwaway
    subprocess.check_call([
        "qemu-img", "create", "-q", "-f", "qcow2",
        "%s/hd.qcow2" % (target.state_dir), disk_size
    ])
    # reinitialize also the EFI vars storage
    shutil.copy("/usr/share/OVMF/OVMF_VARS.fd", target.state_dir)
    # this is a very ugly hack to be able to override the BIOS image
    # used by QEMU
    target.fsdb.set('qemu-image-bios', '/usr/share/edk2/ovmf/OVMF_CODE.fd')

    # QEMU object
    #
    # Note this object exposes many interface implementations, we'll
    # hook them up later.
    cmdline = [
        "/usr/bin/qemu-system-x86_64",
        "-enable-kvm",
        "-cpu",
        "host",
        "-smp",
        "cpus=2",
        "-m",
        str(ram_megs),
        # FIXME: do VNC tunneling so clients can access
        #
        # VNC ports for accessing remotely and the capture interface
        # (ttbd/conf_00_lib_capture.capture_screenshot_vnc)
        # - share=force-shared: cannot be enabled, otherwise gvnccapture
        #   gets resource unavailable 3
        # - we cannot use unix:PATH fro VNC path, otherwise
        #   gvnccapture can't find it. We can't socat it into a port
        #   because QEMU removes the UNIX socket once closed.
        "-display",
        "vnc=localhost:%(vnc.vnc0.port)s",
        # EFI BIOS
        "-drive",
        "if=pflash,format=raw,readonly,file=%(qemu-image-bios)s",
        "-drive",
        "if=pflash,format=raw,file=%(path)s/OVMF_VARS.fd",
        # Storage
        "-drive",
        "file=%%(path)s/hd.qcow2,if=%s,aio=threads" % sd_iftype,
        "-boot",
        "order=nc",  # for POS over PXE
    ]
    # Create as many consoles as directed
    consolel = []
    for console in consoles:
        # record using socat as a separate process --
        # ttbl.qemu.pc() detects we are powering on PTY consoles
        # and upon power on, will symlink
        # STATE_DIR/console-CONSOLE.write to /dev/pts/WHATEVER so
        # ttbl.console_general_pc() can write to it and read from it.
        cmdline += [
            "-chardev", f"pty,id={console}", "-serial", f"chardev:{console}"
        ]
        console_pc = ttbl.console.general_pc(chunk_size=8,
                                             interchunk_wait=0.15)
        console_pc.crlf = "\r"
        consolel.append((console, console_pc))

    cmdline += extra_cmdline.split()
    qemu_pc = ttbl.qemu.pc(qemu_cmdline=cmdline)

    # The QEMU object exposes a power control interface to start/stop
    power_rail = []
    if nw_name:  # got networking?
        power_rail.append(("tuntap-" + nw_name, ttbl.qemu.network_tap_pc()))
    power_rail.append(("AC", qemu_pc))
    for console in consoles:
        # turn on recording from the console when we turn on the
        # machine FIXME: we might loose a wee bit, we shall be able to
        # do it before, but the problem is before the PTY
        # device/socket for the console does not exist
        power_rail.append((console, console_pc))
    target.interface_add("power", ttbl.power.interface(*power_rail))

    # The QEMU object exposes an image setting interface
    target.interface_add(
        "images",
        ttbl.images.interface(
            ("kernel", qemu_pc),
            ("bios", qemu_pc),
            ("initrd", qemu_pc),
        ))

    # Serial console interface: add all the consoles declared in the
    # command line; the generic object console_pc can read from any of
    # them as it takes as parameter the console name to read off/write
    # the QEMU generated cmdline in
    # STATE_DIR/console-NAME.{read,write}
    ssh_pc = ttbl.console.ssh_pc("root@" + ipv4_addr)
    upid = dict(qemu_pc.upid)
    del upid['name_long']
    ssh_pc.upid_set(qemu_pc.name, **upid)
    consolel = []
    for console in consoles:
        consolel.append((console, console_pc))
    consolel += [
        ("ssh0", ssh_pc),
        ("default", consolel[0][0]),
        ("preferred", "ssh0"),
    ]
    target.interface_add("console", ttbl.console.interface(*consolel))
    target.interface_add(
        "capture",
        ttbl.capture.interface(
            vnc0_screenshot=capture_screenshot_vnc,
            screen="vnc0_screenshot",
        ))

    target.interface_add("debug", ttbl.debug.interface(("x86_64", qemu_pc)))

    ttbl.config.target_add(  # add the trget
        target,
        target_type="qemu-uefi-x86_64",
        tags=dict(
            bsp_models={'x86_64': None},
            bsps=dict(x86_64=dict(linux=True), ),
        ))

    target.add_to_interconnect(  # Network support
        nw_name,
        dict(
            ipv4_addr=ipv4_addr,
            ipv4_prefix_len=24,
            ipv6_addr=ipv6_addr,
            ipv6_prefix_len=104,
            mac_addr=mac_addr,
        ))

    target_pos_setup(
        target,
        nw_name,  # POS setup
        pos_boot_dev,
        consoles[0],
        boot_config='uefi',
        boot_config_fix=None,
        boot_to_normal='normal',
        boot_to_pos='pxe',
        mount_fs='multiroot',
        pos_partsizes=mr_partsizes)
    # make TFTP force PXE boot when pos_mode == pxe
    target.power_on_pre_fns.append(ttbl.dhcp.power_on_pre_pos_setup)
    return target