Remove dependency to pylxd and use requests_unixsocket directly

reviewable/pr18780/r1
Hiroaki Nakamura 9 years ago
parent 05700edc0b
commit f786a3e113

@ -50,16 +50,34 @@ options:
- Define the state of a container. - Define the state of a container.
required: false required: false
default: started default: started
timeout_for_addresses: timeout:
description: description:
- A timeout of waiting for IPv4 addresses are set to the all network - A timeout of one LXC REST API call.
interfaces in the container after starting or restarting. - This is also used as a timeout for waiting until IPv4 addresses
- If this value is equal to or less than 0, Ansible does not are set to the all network interfaces in the container after
wait for IPv4 addresses. starting or restarting.
required: false required: false
default: 0 default: 30
wait_for_ipv4_addresses:
description:
- If this is true, the lxd_module waits until IPv4 addresses
are set to the all network interfaces in the container after
starting or restarting.
required: false
default: false
force_stop:
description:
- If this is true, the lxd_module forces to stop the container
when it stops or restarts the container.
required: false
default: false
unix_socket_path:
description:
- The unix domain socket path for the LXD server.
required: false
default: /var/lib/lxd/unix.socket
requirements: requirements:
- 'pylxd >= 2.0' - 'requests_unixsocket'
notes: notes:
- Containers must have a unique name. If you attempt to create a container - Containers must have a unique name. If you attempt to create a container
with a name that already existed in the users namespace the module will with a name that already existed in the users namespace the module will
@ -156,23 +174,22 @@ lxd_container:
description: the old state of the container description: the old state of the container
returned: when state is started or restarted returned: when state is started or restarted
sample: "stopped" sample: "stopped"
logs: actions:
description: list of actions performed for the container description: list of actions performed for the container
returned: success returned: success
type: list type: list
sample: ["created", "started"] sample: ["create", "start"]
""" """
from distutils.spawn import find_executable from ansible.compat.six.moves.urllib.parse import quote
try: try:
from pylxd.client import Client import requests_unixsocket
from requests.exceptions import ConnectionError
except ImportError: except ImportError:
HAS_PYLXD = False HAS_REQUETS_UNIXSOCKET = False
else: else:
HAS_PYLXD = True HAS_REQUETS_UNIXSOCKET = True
from requests.exceptions import ConnectionError
# LXD_ANSIBLE_STATES is a map of states that contain values of methods used # LXD_ANSIBLE_STATES is a map of states that contain values of methods used
# when a particular state is evoked. # when a particular state is evoked.
@ -214,61 +231,91 @@ class LxdContainerManagement(object):
self.container_name = self.module.params['name'] self.container_name = self.module.params['name']
self.config = self.module.params.get('config', None) self.config = self.module.params.get('config', None)
self.state = self.module.params['state'] self.state = self.module.params['state']
self.timeout_for_addresses = self.module.params['timeout_for_addresses'] self.timeout = self.module.params['timeout']
self.wait_for_ipv4_addresses = self.module.params['wait_for_ipv4_addresses']
self.force_stop = self.module.params['force_stop']
self.addresses = None self.addresses = None
self.client = Client() self.unix_socket_path = self.module.params['unix_socket_path']
self.logs = [] self.base_url = 'http+unix://{0}'.format(quote(self.unix_socket_path, safe=''))
self.session = requests_unixsocket.Session()
self.actions = []
def _send_request(self, method, path, json=None, ok_error_codes=None):
try:
url = self.base_url + path
resp = self.session.request(method, url, json=json, timeout=self.timeout)
resp_json = resp.json()
resp_type = resp_json.get('type', None)
if resp_type == 'error':
if ok_error_codes is not None and resp_json['error_code'] in ok_error_codes:
return resp_json
self.module.fail_json(
msg='error response',
request={'method': method, 'url': url, 'json': json, 'timeout': self.timeout},
response={'json': resp_json}
)
return resp_json
except ConnectionError:
self.module.fail_json(msg='cannot connect to the LXD server', unix_socket_path=self.unix_socket_path)
def _operate_and_wait(self, method, path, json=None):
resp_json = self._send_request(method, path, json=json)
if resp_json['type'] == 'async':
path = '{0}/wait?timeout={1}'.format(resp_json['operation'], self.timeout)
resp_json = self._send_request('GET', path)
return resp_json
def _get_container_state_json(self):
return self._send_request(
'GET', '/1.0/containers/{0}/state'.format(self.container_name),
ok_error_codes=[404]
)
@staticmethod
def _container_state_json_to_module_state(resp_json):
if resp_json['type'] == 'error':
return 'absent'
return ANSIBLE_LXD_STATES[resp_json['metadata']['status']]
def _change_state(self, action, force_stop=False):
json={'action': action, 'timeout': self.timeout}
if force_stop:
json['force'] = True
return self._operate_and_wait('PUT', '/1.0/containers/{0}/state'.format(self.container_name), json)
def _create_container(self): def _create_container(self):
config = self.config.copy() config = self.config.copy()
config['name'] = self.container_name config['name'] = self.container_name
self.client.containers.create(config, wait=True) self._operate_and_wait('POST', '/1.0/containers', config)
# NOTE: get container again for the updated state self.actions.append('creat')
self.container = self._get_container()
self.logs.append('created')
def _start_container(self): def _start_container(self):
self.container.start(wait=True) self._change_state('start')
self.logs.append('started') self.actions.append('start')
def _stop_container(self): def _stop_container(self):
self.container.stop(wait=True) self._change_state('stop', self.force_stop)
self.logs.append('stopped') self.actions.append('stop')
def _restart_container(self): def _restart_container(self):
self.container.restart(wait=True) self._change_state('restart', self.force_stop)
self.logs.append('restarted') self.actions.append('restart')
def _delete_container(self): def _delete_container(self):
self.container.delete(wait=True) return self._operate_and_wait('DELETE', '/1.0/containers/{0}'.format(self.container_name))
self.logs.append('deleted') self.actions.append('delete')
def _freeze_container(self): def _freeze_container(self):
self.container.freeze(wait=True) self._change_state('freeze')
self.logs.append('freezed') self.actions.append('freeze')
def _unfreeze_container(self): def _unfreeze_container(self):
self.container.unfreeze(wait=True) self._change_state('unfreeze')
self.logs.append('unfreezed') self.actions.append('unfreez')
def _get_container(self):
try:
return self.client.containers.get(self.container_name)
except NameError:
return None
except ConnectionError:
self.module.fail_json(msg="Cannot connect to lxd server")
@staticmethod
def _container_to_module_state(container):
if container is None:
return "absent"
else:
return ANSIBLE_LXD_STATES[container.status]
def _container_ipv4_addresses(self, ignore_devices=['lo']): def _container_ipv4_addresses(self, ignore_devices=['lo']):
container = self._get_container() resp_json = self._get_container_state_json()
network = container is not None and container.state().network or {} network = resp_json['metadata']['network'] or {}
network = dict((k, v) for k, v in network.iteritems() if k not in ignore_devices) or {} network = dict((k, v) for k, v in network.iteritems() if k not in ignore_devices) or {}
addresses = dict((k, [a['address'] for a in v['addresses'] if a['family'] == 'inet']) for k, v in network.iteritems()) or {} addresses = dict((k, [a['address'] for a in v['addresses'] if a['family'] == 'inet']) for k, v in network.iteritems()) or {}
return addresses return addresses
@ -278,95 +325,80 @@ class LxdContainerManagement(object):
return len(addresses) > 0 and all([len(v) > 0 for v in addresses.itervalues()]) return len(addresses) > 0 and all([len(v) > 0 for v in addresses.itervalues()])
def _get_addresses(self): def _get_addresses(self):
if self.timeout_for_addresses <= 0: due = datetime.datetime.now() + datetime.timedelta(seconds=self.timeout)
return
due = datetime.datetime.now() + datetime.timedelta(seconds=self.timeout_for_addresses)
while datetime.datetime.now() < due: while datetime.datetime.now() < due:
time.sleep(1) time.sleep(1)
addresses = self._container_ipv4_addresses() addresses = self._container_ipv4_addresses()
if self._has_all_ipv4_addresses(addresses): if self._has_all_ipv4_addresses(addresses):
self.addresses = addresses self.addresses = addresses
return return
self._on_timeout()
def _started(self): state_changed = len(self.actions) > 0
"""Ensure a container is started. self.module.fail_json(
failed=True,
msg='timeout for getting IPv4 addresses',
changed=state_changed,
logs=self.actions)
If the container does not exist the container will be created. def _started(self):
""" if self.old_state == 'absent':
if self.container is None:
self._create_container() self._create_container()
self._start_container() self._start_container()
else: else:
if self.container.status == 'Frozen': if self.old_state == 'frozen':
self._unfreeze_container() self._unfreeze_container()
if self.container.status != 'Running': if self.wait_for_ipv4_addresses:
self._start_container() self._get_addresses()
self._get_addresses()
def _stopped(self): def _stopped(self):
if self.container is None: if self.old_state == 'absent':
self._create_container() self._create_container()
else: else:
if self.container.status == 'Frozen': if self.old_state == 'frozen':
self._unfreeze_container() self._unfreeze_container()
if self.container.status != 'Stopped': self._stop_container()
self._stop_container()
def _restarted(self): def _restarted(self):
if self.container is None: if self.old_state == 'absent':
self._create_container() self._create_container()
self._start_container() self._start_container()
else: else:
if self.container.status == 'Frozen': if self.old_state == 'frozen':
self._unfreeze_container() self._unfreeze_container()
if self.container.status == 'Running': self._restart_container()
self._restart_container() if self.wait_for_ipv4_addresses:
else: self._get_addresses()
self._start_container()
self._get_addresses()
def _destroyed(self): def _destroyed(self):
if self.container is not None: if self.old_state != 'absent':
if self.container.status == 'Frozen': if self.old_state == 'frozen':
self._unfreeze_container() self._unfreeze_container()
if self.container.status == 'Running': self._stop_container()
self._stop_container()
self._delete_container() self._delete_container()
def _frozen(self): def _frozen(self):
if self.container is None: if self.old_state == 'absent':
self._create_container() self._create_container()
self._start_container() self._start_container()
self._freeze_container() self._freeze_container()
else: else:
if self.container.status != 'Frozen': if self.old_state == 'stopped':
if self.container.status != 'Running': self._start_container()
self._start_container() self._freeze_container()
self._freeze_container()
def _on_timeout(self):
state_changed = len(self.logs) > 0
self.module.fail_json(
failed=True,
msg='timeout for getting addresses',
changed=state_changed,
logs=self.logs)
def run(self): def run(self):
"""Run the main method.""" """Run the main method."""
self.container = self._get_container() self.old_state = self._container_state_json_to_module_state(self._get_container_state_json())
self.old_state = self._container_to_module_state(self.container)
action = getattr(self, LXD_ANSIBLE_STATES[self.state]) action = getattr(self, LXD_ANSIBLE_STATES[self.state])
action() action()
state_changed = len(self.logs) > 0 state_changed = len(self.actions) > 0
result_json = { result_json = {
"changed" : state_changed, 'changed': state_changed,
"old_state" : self.old_state, 'old_state': self.old_state,
"logs" : self.logs 'actions': self.actions
} }
if self.addresses is not None: if self.addresses is not None:
result_json['addresses'] = self.addresses result_json['addresses'] = self.addresses
@ -389,17 +421,29 @@ def main():
choices=LXD_ANSIBLE_STATES.keys(), choices=LXD_ANSIBLE_STATES.keys(),
default='started' default='started'
), ),
timeout_for_addresses=dict( timeout=dict(
type='int', type='int',
default=0 default=30
),
wait_for_ipv4_addresses=dict(
type='bool',
default=True
),
force_stop=dict(
type='bool',
default=False
),
unix_socket_path=dict(
type='str',
default='/var/lib/lxd/unix.socket'
) )
), ),
supports_check_mode=False, supports_check_mode=False,
) )
if not HAS_PYLXD: if not HAS_REQUETS_UNIXSOCKET:
module.fail_json( module.fail_json(
msg='The `pylxd` module is not importable. Check the requirements.' msg='The `requests_unixsocket` module is not importable. Check the requirements.'
) )
lxd_manage = LxdContainerManagement(module=module) lxd_manage = LxdContainerManagement(module=module)

Loading…
Cancel
Save