<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 async def add_machine( self, spec=None, constraints=None, disks=None, series=None):
 """Start a new, empty machine and optionally a container, or add a container to a machine. :param str spec: Machine specification Examples:: (None) - starts a new machine 'lxd' - starts a new machine with one lxd container 'lxd:4' - starts a new lxd container on machine 4 'ssh:user@10.10.0.3:/path/to/private/key' - manually provision a machine with ssh and the private key used for authentication 'zone=us-east-1a' - starts a machine in zone us-east-1s on AWS 'maas2.name' - acquire machine maas2.name on MAAS :param dict constraints: Machine constraints, which can contain the the following keys:: arch : str container : str cores : int cpu_power : int instance_type : str mem : int root_disk : int spaces : list(str) tags : list(str) virt_type : str Example:: constraints={ 'mem': 256 * MB, 'tags': ['virtual'], } :param list disks: List of disk constraint dictionaries, which can contain the following keys:: count : int pool : str size : int Example:: disks=[{ 'pool': 'rootfs', 'size': 10 * GB, 'count': 1, }] :param str series: Series, e.g. 'xenial' Supported container types are: lxd, kvm When deploying a container to an existing machine, constraints cannot be used. """

        params = client.AddMachineParams()

        if spec:
            if spec.startswith("ssh:"):
                placement, target, private_key_path = spec.split(":")
                user, host = target.split("@")

                sshProvisioner = provisioner.SSHProvisioner(
                    host=host,
                    user=user,
                    private_key_path=private_key_path,
                )

                params = sshProvisioner.provision_machine()
            else:
                placement = parse_placement(spec)
                if placement:
                    params.placement = placement[0]

        params.jobs = ['JobHostUnits']

        if constraints:
            params.constraints = client.Value.from_json(constraints)

        if disks:
            params.disks = [
                client.Constraints.from_json(o) for o in disks]

        if series:
            params.series = series

        # Submit the request.
        client_facade = client.ClientFacade.from_connection(self.connection())
        results = await client_facade.AddMachines([params])
        error = results.machines[0].error
        if error:
            raise ValueError("Error adding machine: %s" % error.message)
        machine_id = results.machines[0].machine

        if spec:
            if spec.startswith("ssh:"):
                # Need to run this after AddMachines has been called,
                # as we need the machine_id
                await sshProvisioner.install_agent(
                    self.connection(),
                    params.nonce,
                    machine_id,
                )

        log.debug('Added new machine %s', machine_id)
        return await self._wait_for_new('machine', machine_id)