juju / python-libjuju

Python library for the Juju API
Apache License 2.0
59 stars 99 forks source link

Manual provisioning of existing machines does not work #106

Closed ThomasBri closed 6 years ago

ThomasBri commented 7 years ago

Trying to add an existing machine to a model via ssh using the model.add_machine method like this:

await model.add_machine(spec='ssh:ubuntu@{}'.format(ip_address))

raises the following error:

ValueError: ('Error adding machine: %s', 'invalid model name "ssh"')

tvansteenburgh commented 7 years ago

Here's the code that was used:

async def add_machine(ip, model):
    ssh_ubuntu__format = 'ssh:ubuntu@{}'.format(ip)
    m = await model.add_machine(spec=ssh_ubuntu__format)
    return m

async def test():
    model = Model()
    await model.connect('xx.xx.xx.xx:17070', 'd06c23f7-aacf-44c8-8819-166b9baf3860', 'xxxx', 'xxxxx')

    print('Start adding machine')
    print(await add_machine('xx.xx.xx.xx', model))

    print('Finished')
    await model.disconnect()

And here's the log:

DEBUG:websockets.protocol:client >> Frame(fin=True, opcode=1, data=b'{\n  "request-id": 4,\n  "request": "AddMachines",\n  "params": {\n    "params": [\n      {\n        "placement": {\n          "scope": "ssh",\n          "directive": "ubuntu@192.168.77.242"\n        },\n        "parent-id": null,\n        "addresses": [],\n        "disks": [],\n        "jobs": [\n          "JobHostUnits"\n        ],\n        "hardware-characteristics": null,\n        "nonce": null,\n        "container-type": null,\n        "constraints": null,\n        "series": null,\n        "instance-id": null\n      }\n    ]\n  },\n  "version": 1,\n  "type": "Client"\n}')
WARNING:asyncio:Executing <Task pending coro=<test() running at /opt/openbaton/jujunew/test/new.py:178> wait_for=<Future pending cb=[Task._wakeup()] created at /usr/lib/python3.5/asyncio/base_events.py:252> cb=[_run_until_complete_cb() at /usr/lib/python3.5/asyncio/base_events.py:164] created at /usr/lib/python3.5/asyncio/base_events.py:367> took 3.741 seconds
DEBUG:asyncio:poll took 0.051 ms: 1 events
DEBUG:websockets.protocol:client << Frame(fin=True, opcode=1, data=b'{"request-id":4,"response":{"machines":[{"machine":"","error":{"message":"invalid model name \\"ssh\\"","code":""}}]}}\n')
Traceback (most recent call last):
  File "/usr/local/lib/pycharm-2016.3.2/helpers/pydev/pydevd.py", line 1596, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/usr/local/lib/pycharm-2016.3.2/helpers/pydev/pydevd.py", line 974, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/usr/local/lib/pycharm-2016.3.2/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/opt/openbaton/jujunew/test/new.py", line 218, in <module>
    exec_and_wait(test())
  File "/opt/openbaton/jujunew/test/new.py", line 215, in exec_and_wait
    return loop.run_until_complete(routine)
  File "/usr/lib/python3.5/asyncio/base_events.py", line 387, in run_until_complete
    return future.result()
  File "/usr/lib/python3.5/asyncio/futures.py", line 274, in result
    raise self._exception
  File "/usr/lib/python3.5/asyncio/tasks.py", line 239, in _step
    result = coro.send(None)
  File "/opt/openbaton/jujunew/test/new.py", line 178, in test
    print(await add_machine('192.168.77.242', model))
  File "/usr/lib/python3.5/asyncio/coroutines.py", line 105, in __next__
    return self.gen.send(None)
  File "/opt/openbaton/jujunew/test/new.py", line 168, in add_machine
    m = await model.add_machine(spec=ssh_ubuntu__format)
  File "/usr/lib/python3.5/asyncio/coroutines.py", line 105, in __next__
    return self.gen.send(None)
  File "/home/tbr/.virtualenvs/jujuvnfm/lib/python3.5/site-packages/juju/model.py", line 795, in add_machine
    raise ValueError("Error adding machine: %s", error.message)
ValueError: ('Error adding machine: %s', 'invalid model name "ssh"')
ThomasBri commented 7 years ago

The actual ws json request is:

{
  "request": "AddMachines",
  "type": "Client",
  "params": {
    "params": [
      {
        "jobs": [
          "JobHostUnits"
        ],
        "placement": {
          "directive": "ubuntu@xx.xx.xx.xx",
          "scope": "ssh"
        },
        "nonce": null,
        "disks": [],
        "parent-id": null,
        "container-type": null,
        "addresses": [],
        "hardware-characteristics": null,
        "instance-id": null,
        "series": null,
        "constraints": null
      }
    ]
  },
  "version": 1,
  "request-id": 4
}
pengale commented 7 years ago

It sounds like the parse function in juju/placement.py is doing the wrong thing here. I'm assuming that instead of this:

        "placement": {
          "directive": "ubuntu@xx.xx.xx.xx",
          "scope": "ssh"
        },

We want this:

        "placement": {
          "directive": "ssh:ubuntu@xx.xx.xx.xx",
          "scope": "<some valid scope, or possibly null>"
        },

parse.py is basically one big hack to get around the fact that the planner doesn't give us placement directives that we can actually pass back to it, so it wouldn't be too terrible if we added one more hack to it, to make it treat "ssh:..." as a special case.

pengale commented 7 years ago

Follow-up: instead of hacking parse.py, you could also pass in an instance of client.Placement to the spec arg of model.add_machine, rather than the bare "ssh:..." string. That will cause parse.py to skip over it entirely.

ThomasBri commented 7 years ago

It seems that changing the placement is not enough here. Variations of something like

placement = Placement(scope='model-uuid', directive='ssh:ubuntu@{}'.format(ip)) await model.add_machine(spec=placement)

did not work for me. According to Juju's log when using the CLI, the request for manual provisioning looks like this:

{  
   "request-id":2,
   "type":"Client",
   "version":1,
   "request":"AddMachinesV2",
   "params":{  
      "params":[  
         {  
            "series":"trusty",
            "constraints":{  },
            "jobs":[  
               "JobHostUnits"
            ],
            "parent-id":"",
            "container-type":"",
            "instance-id":"manual:xx.xx.xx.xx",
            "nonce":"manual:xx.xx.xx.xx:40d94bb5-6271-243b-81c2-5bf85671bf86",
            "hardware-characteristics":{  
               "arch":"amd64",
               "mem":993,
               "cpu-cores":1
            },
            "addresses":[  
               {  
                  "value":"xx.xx.xx.xx",
                  "type":"ipv4",
                  "scope":"public"
               }
            ]
         }
      ]
   }
}

I tried using the client module's AddMachinesV2 method and passing these parameters, which resulted in the same request to the API. However, manual provisioning seems to require more than that since the machine added by this call remained in the pending state and there were no logs created on the actual virtual machine as it was the case when using the CLI.

It would be very helpful to know the exact steps and API calls executed in the manual provisioning procedure.

lorenzotomasini commented 7 years ago

Hi,

We are still struggling with this issue. Is there any update?

Thanks

jsrz commented 6 years ago

I've come across this issue as well.

A work around for this is to use the python 'sh' module that you can install with pip. Import with "from sh import juju". Then run "juju('add-machine', ...)" in your code.

If you are having issues with the host key/finger print when calling add machine this way. You can pull it in to the host calling juju add machine by running something like: from sh import ssh ssh('-oStrictHostKeyChecking=no', user_and_machine, 'exit')

Only issue is, since StrictHostKeyChecking it turned off, you open yourself up to man in the middle shenanigans.

AdamIsrael commented 6 years ago

The core problem is that there are several steps that the equivalent Juju CLI command do, outside of the API, that we need to re-implement. These include:

Only after all that is done is the call to AddMachinesV2 called.

johnsca commented 6 years ago

This was fixed in #240