Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
587f0d9c0b | ||
|
|
36773c7bb3 | ||
|
|
3d9917b8a7 | ||
|
|
a166ce1c8d | ||
|
|
e81bf1ca16 | ||
|
|
2c95c6ef0c | ||
|
|
18067b82b7 | ||
|
|
bc317d1438 | ||
|
|
e17c8be53a | ||
|
|
2014863c37 | ||
|
|
4b57777a2c |
@@ -1,12 +1,12 @@
|
||||
# decort-ansible
|
||||
Ansible modules for Digital Energy Orchestration Technology (DECORT) platform v3.4.2 and above
|
||||
Ansible modules for Digital Energy Orchestration Technology (DECORT) platform v3.6.1 and above.
|
||||
|
||||
Note that this module may produce unreliable results when used with older DECORT API versions.
|
||||
|
||||
Requirements:
|
||||
* Ansible 2.7 or higher
|
||||
* Python 2.6 or higher
|
||||
* PyJWT Python module
|
||||
* Python 3.7 or higher
|
||||
* PyJWT 2.0.0 Python module or higher
|
||||
* requests Python module
|
||||
* netaddr Python module
|
||||
* DECORT cloud platform version 3.4.2 or higher
|
||||
* DECORT cloud platform version 3.5.0 or higher
|
||||
|
||||
@@ -188,3 +188,136 @@
|
||||
var: my_pfw.facts
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create k8s cluster with params
|
||||
decort_k8s:
|
||||
authenticator: jwt
|
||||
jwt: "{{ token.jwt }}"
|
||||
controller_url: "{{ decort_ctrl }}"
|
||||
k8s_name: "k8s_cluster_name"
|
||||
wg_name: "k8s_wg_name"
|
||||
k8ci_id: "{{ k8ci_id }}"
|
||||
rg_id: "{{ my_rg.facts.id }}"
|
||||
master_count: 1
|
||||
master_cpu: 2
|
||||
master_ram_mb: 2048
|
||||
master_disk_gb: 20
|
||||
worker_count: 3
|
||||
worker_cpu: 1
|
||||
worker_ram_mb: 1024
|
||||
worker_disk_gb: 20
|
||||
extnet_id: "{{ target_ext_net_id }}"
|
||||
with_lb: True
|
||||
state: present
|
||||
register: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: print out the result
|
||||
debug:
|
||||
var: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Disable k8s cluster
|
||||
decort_k8s:
|
||||
authenticator: jwt
|
||||
jwt: "{{ token.jwt }}"
|
||||
controller_url: "{{ decort_ctrl }}"
|
||||
k8s_name: "k8s_cluster_name"
|
||||
wg_name: "k8s_wg_name"
|
||||
k8ci_id: "{{ k8ci_id }}"
|
||||
rg_id: "{{ my_rg.facts.id }}"
|
||||
state: disabled
|
||||
register: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: print out the result
|
||||
debug:
|
||||
var: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Delete in trash k8s cluster
|
||||
decort_k8s:
|
||||
authenticator: jwt
|
||||
jwt: "{{ token.jwt }}"
|
||||
controller_url: "{{ decort_ctrl }}"
|
||||
k8s_name: "k8s_cluster_name"
|
||||
wg_name: "k8s_wg_name"
|
||||
k8ci_id: "{{ k8ci_id }}"
|
||||
rg_id: "{{ my_rg.facts.id }}"
|
||||
state: absent
|
||||
permanent: False
|
||||
register: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: print out the result
|
||||
debug:
|
||||
var: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Restore from trash deleted k8s cluster
|
||||
decort_k8s:
|
||||
authenticator: jwt
|
||||
jwt: "{{ token.jwt }}"
|
||||
controller_url: "{{ decort_ctrl }}"
|
||||
k8s_name: "k8s_cluster_name"
|
||||
wg_name: "k8s_wg_name"
|
||||
k8ci_id: "{{ k8ci_id }}"
|
||||
rg_id: "{{ my_rg.facts.id }}"
|
||||
state: enabled
|
||||
register: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: print out the result
|
||||
debug:
|
||||
var: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Enable k8s cluster
|
||||
decort_k8s:
|
||||
authenticator: jwt
|
||||
jwt: "{{ token.jwt }}"
|
||||
controller_url: "{{ decort_ctrl }}"
|
||||
k8s_name: "k8s_cluster_name"
|
||||
wg_name: "k8s_wg_name"
|
||||
k8ci_id: "{{ k8ci_id }}"
|
||||
rg_id: "{{ my_rg.facts.id }}"
|
||||
state: enabled
|
||||
register: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Enable k8s cluster
|
||||
decort_k8s:
|
||||
authenticator: jwt
|
||||
jwt: "{{ token.jwt }}"
|
||||
controller_url: "{{ decort_ctrl }}"
|
||||
k8s_name: "k8s_cluster_name"
|
||||
wg_name: "k8s_wg_name"
|
||||
k8ci_id: "{{ k8ci_id }}"
|
||||
rg_id: "{{ my_rg.facts.id }}"
|
||||
state: enabled
|
||||
started: True
|
||||
register: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: print out the result
|
||||
debug:
|
||||
var: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Destroy k8s cluster
|
||||
decort_k8s:
|
||||
authenticator: jwt
|
||||
jwt: "{{ token.jwt }}"
|
||||
controller_url: "{{ decort_ctrl }}"
|
||||
k8s_name: "k8s_cluster_name"
|
||||
wg_name: "k8s_wg_name"
|
||||
k8ci_id: "{{ k8ci_id }}"
|
||||
rg_id: "{{ my_rg.facts.id }}"
|
||||
state: absent
|
||||
permanent: True
|
||||
register: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
- name: print out the result
|
||||
debug:
|
||||
var: k8s
|
||||
delegate_to: localhost
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
||||
# Copyright: (c) 2018-2020 Digital Energy Cloud Solutions LLC
|
||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
||||
#
|
||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
#
|
||||
@@ -30,7 +30,7 @@ requirements:
|
||||
- requests Python module
|
||||
- netaddr Python module
|
||||
- decort_utils utility library (module)
|
||||
- DECORT cloud platform version 3.4.2 or higher
|
||||
- DECORT cloud platform version 3.6.1 or higher
|
||||
notes:
|
||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
||||
@@ -139,7 +139,7 @@ options:
|
||||
description:
|
||||
- Name of the pool where to place new disk. Once disk is created, its pool cannot be changed.
|
||||
- This parameter is used when creating new disk and igonred for all other operations.
|
||||
default: default
|
||||
default: empty string
|
||||
required: no
|
||||
sep_id:
|
||||
description:
|
||||
@@ -209,7 +209,6 @@ EXAMPLES = '''
|
||||
controller_url: "https://cloud.digitalenergy.online"
|
||||
name: "MyDataDisk01"
|
||||
sep_id: 1
|
||||
pool: "default"
|
||||
size: 50
|
||||
account_name: "MyAccount"
|
||||
state: present
|
||||
@@ -275,7 +274,7 @@ def decort_disk_package_facts(disk_facts, check_mode=False):
|
||||
ret_dict['size'] = disk_facts['sizeMax']
|
||||
ret_dict['state'] = disk_facts['status']
|
||||
ret_dict['account_id'] = disk_facts['accountId']
|
||||
ret_dict['sep_id'] = disk_facts['sepid']
|
||||
ret_dict['sep_id'] = disk_facts['sepId']
|
||||
ret_dict['pool'] = disk_facts['pool']
|
||||
ret_dict['attached_to'] = disk_facts['vmid']
|
||||
ret_dict['gid'] = disk_facts['gid']
|
||||
@@ -316,7 +315,7 @@ def decort_disk_parameters():
|
||||
fallback=(env_fallback, ['DECORT_PASSWORD']),
|
||||
no_log=True),
|
||||
place_with=dict(type='int', required=False, default=0),
|
||||
pool=dict(type='str', required=False, default='default'),
|
||||
pool=dict(type='str', required=False, default=''),
|
||||
sep_id=dict(type='int', required=False, default=0),
|
||||
size=dict(type='int', required=False),
|
||||
state=dict(type='str',
|
||||
@@ -402,7 +401,7 @@ def main():
|
||||
|
||||
disk_should_exist = False
|
||||
target_sep_id = 0
|
||||
target_pool = "default"
|
||||
# target_pool = ""
|
||||
|
||||
if disk_id:
|
||||
disk_should_exist = True
|
||||
@@ -455,10 +454,10 @@ def main():
|
||||
# request to place this disk on the same SEP as the specified OS image
|
||||
# validate specified OS image and assign SEP ID accordingly
|
||||
image_id, image_facts = decon.image_find(amodule.params['place_with'], "", 0)
|
||||
target_sep_id = image_facts['sepid']
|
||||
target_sep_id = image_facts['sepId']
|
||||
else:
|
||||
# no new SEP ID is explicitly specified, and no place_with option - use sep_id from the disk_facts
|
||||
target_sep_id = disk_facts['sepid']
|
||||
# no new SEP ID is explicitly specified, and no place_with option - use sepId from the disk_facts
|
||||
target_sep_id = disk_facts['sepId']
|
||||
disk_id = decon.disk_provision(disk_name=disk_facts['name'], # as this disk was found, its name is in the facts
|
||||
size=amodule.params['size'],
|
||||
account_id=validated_acc_id,
|
||||
@@ -496,7 +495,7 @@ def main():
|
||||
# request to place this disk on the same SEP as the specified OS image
|
||||
# validate specified OS image and assign SEP ID accordingly
|
||||
image_id, image_facts = decon.image_find(amodule.params['place_with'], "", 0)
|
||||
target_sep_id = image_facts['sepid']
|
||||
target_sep_id = image_facts['sepId']
|
||||
else:
|
||||
# no SEP ID is explicitly specified, and no place_with option - we do not know where
|
||||
# to place the new disk - fail the module
|
||||
@@ -509,7 +508,7 @@ def main():
|
||||
size=amodule.params['size'],
|
||||
account_id=validated_acc_id,
|
||||
sep_id=target_sep_id,
|
||||
pool=amodule.params['pool'],
|
||||
pool_name=amodule.params['pool'],
|
||||
desc=amodule.params['annotation'],
|
||||
location="")
|
||||
disk_should_exist = True
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
||||
# Copyright: (c) 2018-2020 Digital Energy Cloud Solutions LLC
|
||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
||||
#
|
||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
#
|
||||
@@ -32,7 +32,7 @@ requirements:
|
||||
- PyJWT module
|
||||
- requests module
|
||||
- decort_utils utility library (module)
|
||||
- DECORT cloud platform version 3.4.2 or higher
|
||||
- DECORT cloud platform version 3.6.1 or higher
|
||||
options:
|
||||
app_id:
|
||||
description:
|
||||
|
||||
236
library/decort_k8s.py
Normal file
236
library/decort_k8s.py
Normal file
@@ -0,0 +1,236 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
||||
#
|
||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
#
|
||||
|
||||
#
|
||||
# Author: Aleksandr Malyavin (aleksandr.malyavin@digitalenergy.online)
|
||||
#
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import env_fallback
|
||||
|
||||
from ansible.module_utils.decort_utils import *
|
||||
|
||||
|
||||
def decort_k8s_package_facts(arg_k8s_facts, arg_check_mode=False):
|
||||
"""Package a dictionary of k8s facts according to the decort_k8s module specification. This dictionary will
|
||||
be returned to the upstream Ansible engine at the completion of the module run.
|
||||
|
||||
@param arg_k8s_facts: dictionary with k8s facts as returned by API call to .../k8s/get
|
||||
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
|
||||
"""
|
||||
|
||||
ret_dict = dict(id=0,
|
||||
name="none",
|
||||
state="CHECK_MODE",
|
||||
)
|
||||
|
||||
if arg_check_mode:
|
||||
# in check mode return immediately with the default values
|
||||
return ret_dict
|
||||
|
||||
if arg_k8s_facts is None:
|
||||
# if void facts provided - change state value to ABSENT and return
|
||||
ret_dict['state'] = "ABSENT"
|
||||
return ret_dict
|
||||
|
||||
ret_dict['id'] = arg_k8s_facts['id']
|
||||
ret_dict['name'] = arg_k8s_facts['name']
|
||||
ret_dict['techStatus'] = arg_k8s_facts['techStatus']
|
||||
ret_dict['state'] = arg_k8s_facts['status']
|
||||
|
||||
return ret_dict
|
||||
|
||||
def decort_k8s_parameters():
|
||||
"""Build and return a dictionary of parameters expected by decort_k8s module in a form accepted
|
||||
by AnsibleModule utility class."""
|
||||
|
||||
return dict(
|
||||
account_id=dict(type='int', required=False),
|
||||
account_name=dict(type='str', required=False, default=''),
|
||||
annotation=dict(type='str', required=False, default=''),
|
||||
app_id=dict(type='str',
|
||||
required=False,
|
||||
fallback=(env_fallback, ['DECORT_APP_ID'])),
|
||||
app_secret=dict(type='str',
|
||||
required=False,
|
||||
fallback=(env_fallback, ['DECORT_APP_SECRET']),
|
||||
no_log=True),
|
||||
authenticator=dict(type='str',
|
||||
required=True,
|
||||
choices=['legacy', 'oauth2', 'jwt']),
|
||||
controller_url=dict(type='str', required=True),
|
||||
# datacenter=dict(type='str', required=False, default=''),
|
||||
jwt=dict(type='str',
|
||||
required=False,
|
||||
fallback=(env_fallback, ['DECORT_JWT']),
|
||||
no_log=True),
|
||||
oauth2_url=dict(type='str',
|
||||
required=False,
|
||||
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
|
||||
password=dict(type='str',
|
||||
required=False,
|
||||
fallback=(env_fallback, ['DECORT_PASSWORD']),
|
||||
no_log=True),
|
||||
quotas=dict(type='dict', required=False),
|
||||
state=dict(type='str',
|
||||
default='present',
|
||||
choices=['absent', 'disabled', 'enabled', 'present']),
|
||||
permanent=dict(type='bool', default=False),
|
||||
started=dict(type='bool', default=True),
|
||||
user=dict(type='str',
|
||||
required=False,
|
||||
fallback=(env_fallback, ['DECORT_USER'])),
|
||||
k8s_name=dict(type='str', required=True),
|
||||
rg_id=dict(type='int', required=True),
|
||||
k8ci_id=dict(type='int', required=True),
|
||||
wg_name=dict(type='str', required=True),
|
||||
master_count=dict(type='int', default=1),
|
||||
master_cpu=dict(type='int', default=2),
|
||||
master_ram_mb=dict(type='int', default=2048),
|
||||
master_disk_gb=dict(type='int', default=10),
|
||||
worker_count=dict(type='int', default=1),
|
||||
worker_cpu=dict(type='int', default=1),
|
||||
worker_ram_mb=dict(type='int', default=1024),
|
||||
worker_disk_gb=dict(type='int', default=0),
|
||||
extnet_id=dict(type='int', default=0),
|
||||
description=dict(type='str', default="Created by decort ansible module"),
|
||||
with_lb=dict(type='bool', default=True),
|
||||
verify_ssl=dict(type='bool', required=False, default=True),
|
||||
workflow_callback=dict(type='str', required=False),
|
||||
workflow_context=dict(type='str', required=False),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
module_parameters = decort_k8s_parameters()
|
||||
|
||||
amodule = AnsibleModule(argument_spec=module_parameters,
|
||||
supports_check_mode=True,
|
||||
mutually_exclusive=[
|
||||
['oauth2', 'password'],
|
||||
['password', 'jwt'],
|
||||
['jwt', 'oauth2'],
|
||||
],
|
||||
required_together=[
|
||||
['app_id', 'app_secret'],
|
||||
['user', 'password'],
|
||||
],
|
||||
)
|
||||
|
||||
decon = DecortController(amodule)
|
||||
k8s_id, k8s_facts = decon.k8s_find(arg_k8s_name=amodule.params['k8s_name'],
|
||||
arg_check_state=False)
|
||||
k8s_should_exist = True
|
||||
|
||||
if k8s_id:
|
||||
if k8s_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CREATING",
|
||||
"RESTORING"] and amodule.params['state'] != "present":
|
||||
decon.result['failed'] = True
|
||||
decon.result['changed'] = False
|
||||
decon.result['msg'] = ("No change can be done for existing k8s ID {} because of its current "
|
||||
"status '{}'").format(k8s_id, k8s_facts['status'])
|
||||
elif k8s_facts['status'] in ["DISABLED", "ENABLED", "CREATED", "DELETED"] and amodule.params['state'] == "absent":
|
||||
if amodule.params['permanent'] is True:
|
||||
decon.k8s_delete(k8s_id, True)
|
||||
k8s_facts['status'] = 'DESTROYED'
|
||||
k8s_should_exist = False
|
||||
else:
|
||||
decon.k8s_delete(k8s_id)
|
||||
k8s_facts['status'] = 'DELETED'
|
||||
k8s_should_exist = True
|
||||
elif k8s_facts['status'] == "ENABLED" and amodule.params['started'] is True:
|
||||
decon.k8s_state(k8s_facts, amodule.params['state'], amodule.params['started'])
|
||||
elif k8s_facts['status'] == amodule.params['state'].upper():
|
||||
decon.k8s_state(k8s_facts, amodule.params['state'])
|
||||
elif k8s_facts['status'] in ["ENABLED", "CREATED"] and amodule.params['state'] == "disabled":
|
||||
decon.k8s_state(k8s_facts, 'disabled')
|
||||
elif k8s_facts['status'] in ["DISABLED", "CREATED"]:
|
||||
if amodule.params['state'] == 'enabled':
|
||||
decon.k8s_state(k8s_facts, 'enabled', amodule.params['started'])
|
||||
elif amodule.params['state'] == "disabled":
|
||||
decon.k8s_state(k8s_facts, 'disabled')
|
||||
k8s_should_exist = True
|
||||
elif k8s_facts['status'] == "DELETED":
|
||||
if amodule.params['state'] in ('enabled', 'present'):
|
||||
decon.k8s_restore(k8s_id)
|
||||
k8s_should_exist = True
|
||||
elif amodule.params['state'] == 'disabled':
|
||||
decon.result['failed'] = True
|
||||
decon.result['changed'] = False
|
||||
decon.result['msg'] = ("Invalid target state '{}' requested for k8s ID {} in the "
|
||||
"current status '{}'").format(k8s_id,
|
||||
amodule.params['state'],
|
||||
k8s_facts['status'])
|
||||
k8s_should_exist = False
|
||||
elif k8s_facts['status'] == "DESTROYED":
|
||||
if amodule.params['state'] in ('present', 'enabled'):
|
||||
k8s_should_exist = True
|
||||
elif amodule.params['state'] == 'absent':
|
||||
# nop
|
||||
decon.result['failed'] = False
|
||||
decon.result['changed'] = False
|
||||
decon.result['msg'] = ("No state change required for k8s ID {} because of its "
|
||||
"current status '{}'").format(k8s_id,
|
||||
k8s_facts['status'])
|
||||
k8s_should_exist = False
|
||||
elif amodule.params['state'] == 'disabled':
|
||||
# error
|
||||
decon.result['failed'] = True
|
||||
decon.result['changed'] = False
|
||||
decon.result['msg'] = ("Invalid target state '{}' requested for k8s ID {} in the "
|
||||
"current status '{}'").format(k8s_id,
|
||||
amodule.params['state'],
|
||||
k8s_facts['status'])
|
||||
else:
|
||||
k8s_should_exist = False
|
||||
if amodule.params['state'] == 'absent':
|
||||
decon.result['failed'] = False
|
||||
decon.result['changed'] = False
|
||||
decon.result['msg'] = ("Nothing to do as target state 'absent' was requested for "
|
||||
"non-existent k8s name '{}'").format(amodule.params['k8s_name'])
|
||||
elif amodule.params['state'] in ('present', 'enabled'):
|
||||
decon.check_amodule_argument('k8s_name')
|
||||
k8s_id = decon.k8s_provision(amodule.params['k8s_name'],
|
||||
amodule.params['wg_name'],
|
||||
amodule.params['k8ci_id'],
|
||||
amodule.params['rg_id'],
|
||||
amodule.params['master_count'],
|
||||
amodule.params['master_cpu'],
|
||||
amodule.params['master_ram_mb'],
|
||||
amodule.params['master_disk_gb'],
|
||||
amodule.params['worker_count'],
|
||||
amodule.params['worker_cpu'],
|
||||
amodule.params['worker_ram_mb'],
|
||||
amodule.params['worker_disk_gb'],
|
||||
amodule.params['extnet_id'],
|
||||
amodule.params['with_lb'],
|
||||
amodule.params['description'],
|
||||
)
|
||||
k8s_should_exist = True
|
||||
elif amodule.params['state'] == 'disabled':
|
||||
decon.result['failed'] = True
|
||||
decon.result['changed'] = False
|
||||
decon.result['msg'] = ("Invalid target state '{}' requested for non-existent "
|
||||
"k8s name '{}' ").format(amodule.params['state'],
|
||||
amodule.params['k8s_name'])
|
||||
if decon.result['failed']:
|
||||
amodule.fail_json(**decon.result)
|
||||
else:
|
||||
if k8s_should_exist:
|
||||
if decon.result['changed']:
|
||||
_, k8s_facts = decon.k8s_find(arg_k8s_id=k8s_id)
|
||||
decon.result['facts'] = decort_k8s_package_facts(k8s_facts, amodule.check_mode)
|
||||
amodule.exit_json(**decon.result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
||||
# Copyright: (c) 2018-2020 Digital Energy Cloud Solutions LLC
|
||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
||||
#
|
||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
#
|
||||
@@ -32,7 +32,7 @@ requirements:
|
||||
- requests Python module
|
||||
- netaddr Python module
|
||||
- decort_utils utility library (module)
|
||||
- DECORT cloud platform version 3.4.2 or higher
|
||||
- DECORT cloud platform version 3.6.1 or higher
|
||||
notes:
|
||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
||||
@@ -77,8 +77,8 @@ options:
|
||||
- Architecture of the KVM VM. DECORT supports KVM hosts based on Intel x86 and IBM PowerPC hardware.
|
||||
- This parameter is used when new KVM VM is created and ignored for all other operations.
|
||||
- Module may fail if your DECORT installation does not have physical nodes of specified architecture.
|
||||
default: KVM_X86
|
||||
choices: [ KVM_X86, KVM_PPC ]
|
||||
default: X86_64
|
||||
choices: [ X86_64, PPC64_LE ]
|
||||
required: yes
|
||||
authenticator:
|
||||
description:
|
||||
@@ -439,8 +439,8 @@ class decort_kvmvm(DecortController):
|
||||
check_state=False)
|
||||
|
||||
if self.comp_id:
|
||||
if self.comp_info['status'] != 'DESTROYED' and self.comp_info['arch'] not in ["KVM_X86", "KVM_PPC"]:
|
||||
# If we found a Compute in a non-DESTROYED state and it is not of type KVM_*, abort the module
|
||||
if self.comp_info['status'] != 'DESTROYED' and self.comp_info['arch'] not in ["X86_64", "PPC64_LE"]:
|
||||
# If we found a Compute in a non-DESTROYED state and it is not of type valid arch, abort the module
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = ("Compute ID {} architecture '{}' is not supported by "
|
||||
"decort_kvmvm module.").format(self.comp_id,
|
||||
@@ -500,7 +500,7 @@ class decort_kvmvm(DecortController):
|
||||
self.check_amodule_argument('cpu')
|
||||
self.check_amodule_argument('ram')
|
||||
|
||||
if self.amodule.params['arch'] not in ["KVM_X86", "KVM_PPC"]:
|
||||
if self.amodule.params['arch'] not in ["X86_64", "PPC64_LE"]:
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = ("Unsupported architecture '{}' is specified for "
|
||||
"KVM VM create.").format(self.amodule.params['arch'])
|
||||
@@ -743,7 +743,7 @@ class decort_kvmvm(DecortController):
|
||||
required=False,
|
||||
fallback=(env_fallback, ['DECORT_APP_SECRET']),
|
||||
no_log=True),
|
||||
arch=dict(type='str', choices=['KVM_X86', 'KVM_PPC'], default='KVM_X86'),
|
||||
arch=dict(type='str', choices=['X86_64', 'PPC64_LE'], default='X86_64'),
|
||||
authenticator=dict(type='str',
|
||||
required=True,
|
||||
choices=['legacy', 'oauth2', 'jwt']),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
||||
# Copyright: (c) 2018-2020 Digital Energy Cloud Solutions LLC
|
||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
||||
#
|
||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
#
|
||||
@@ -33,7 +33,7 @@ requirements:
|
||||
- requests Python module
|
||||
- netaddr Python module
|
||||
- decort_utils utility library (module)
|
||||
- DECORT cloud platform version 3.4.2 or higher.
|
||||
- DECORT cloud platform version 3.6.1 or higher.
|
||||
notes:
|
||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
||||
@@ -203,7 +203,7 @@ def decort_osimage_package_facts(arg_osimage_facts, arg_check_mode=False):
|
||||
ret_dict['size'] = arg_osimage_facts['size']
|
||||
ret_dict['type'] = arg_osimage_facts['type']
|
||||
# ret_dict['arch'] = arg_osimage_facts['architecture']
|
||||
ret_dict['sep_id'] = arg_osimage_facts['sepid']
|
||||
ret_dict['sep_id'] = arg_osimage_facts['sepId']
|
||||
ret_dict['pool'] = arg_osimage_facts['pool']
|
||||
ret_dict['state'] = arg_osimage_facts['status']
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
||||
# Copyright: (c) 2018-2020 Digital Energy Cloud Solutions LLC
|
||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
||||
#
|
||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
#
|
||||
@@ -30,7 +30,7 @@ requirements:
|
||||
- requests Python module
|
||||
- netaddr Python module
|
||||
- decort_utils utility library (module)
|
||||
- DECORT cloud platform version 3.4.2 or higher
|
||||
- DECORT cloud platform version 3.6.1 or higher
|
||||
notes:
|
||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
||||
# Copyright: (c) 2018-2020 Digital Energy Cloud Solutions LLC
|
||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
||||
#
|
||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
#
|
||||
@@ -30,7 +30,7 @@ requirements:
|
||||
- requests Python module
|
||||
- netaddr Python module
|
||||
- decort_utils utility library (module)
|
||||
- DECORT cloud platform version 3.4.2 or higher
|
||||
- DECORT cloud platform version 3.6.1 or higher
|
||||
notes:
|
||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
||||
# Copyright: (c) 2018-2020 Digital Energy Cloud Solutions LLC
|
||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
||||
#
|
||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
#
|
||||
@@ -30,7 +30,7 @@ requirements:
|
||||
- requests Python module
|
||||
- netaddr Python module
|
||||
- decort_utils utility library (module)
|
||||
- DECORT cloud platform version 3.4.2 or higher
|
||||
- DECORT cloud platform version 3.6.1 or higher
|
||||
notes:
|
||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
||||
@@ -273,7 +273,7 @@ def decort_vins_package_facts(arg_vins_facts, arg_check_mode=False):
|
||||
ret_dict['name'] = arg_vins_facts['name']
|
||||
ret_dict['state'] = arg_vins_facts['status']
|
||||
ret_dict['account_id'] = arg_vins_facts['accountId']
|
||||
ret_dict['rg_id'] = arg_vins_facts['rgid']
|
||||
ret_dict['rg_id'] = arg_vins_facts['rgId']
|
||||
ret_dict['int_net_addr'] = arg_vins_facts['network']
|
||||
ret_dict['gid'] = arg_vins_facts['gid']
|
||||
|
||||
@@ -290,9 +290,9 @@ def decort_vins_package_facts(arg_vins_facts, arg_check_mode=False):
|
||||
# ??? -> ext_net_id
|
||||
# tech_status -> techStatus
|
||||
|
||||
|
||||
return ret_dict
|
||||
|
||||
|
||||
def decort_vins_parameters():
|
||||
"""Build and return a dictionary of parameters expected by decort_vins module in a form accepted
|
||||
by AnsibleModule utility class."""
|
||||
@@ -342,6 +342,7 @@ def decort_vins_parameters():
|
||||
workflow_context=dict(type='str', required=False),
|
||||
)
|
||||
|
||||
|
||||
# Workflow digest:
|
||||
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
|
||||
# 2) check if the ViNS with this id or name exists under specified account / resource group
|
||||
@@ -385,7 +386,7 @@ def main():
|
||||
decon.fail_json(**decon.result)
|
||||
vins_level = "ID"
|
||||
validated_acc_id = vins_facts['accountId']
|
||||
validated_rg_id = vins_facts['rgid']
|
||||
validated_rg_id = vins_facts['rgId']
|
||||
elif amodule.params['rg_id']:
|
||||
# expect ViNS @ RG level in the RG with specified ID
|
||||
vins_level = "RG"
|
||||
@@ -570,7 +571,6 @@ def main():
|
||||
decon.result['msg'] = ("Invalid target state '{}' requested for non-existent "
|
||||
"ViNS name '{}'").format(amodule.params['state'],
|
||||
amodule.params['vins_name'])
|
||||
|
||||
#
|
||||
# conditional switch end - complete module run
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
||||
# Copyright: (c) 2018-2020 Digital Energy Cloud Solutions LLC
|
||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
||||
#
|
||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
||||
#
|
||||
@@ -28,7 +28,7 @@ Requirements:
|
||||
- PyJWT Python module
|
||||
- requests Python module
|
||||
- netaddr Python module
|
||||
- DECORT cloud platform version 3.4.2 or higher
|
||||
- DECORT cloud platform version 3.6.1 or higher
|
||||
"""
|
||||
|
||||
import copy
|
||||
@@ -40,6 +40,7 @@ import requests
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
#
|
||||
# TODO: the following functionality to be implemented and/or tested
|
||||
# 4) workflow callbacks
|
||||
@@ -150,7 +151,7 @@ class DecortController(object):
|
||||
if self.authenticator == "jwt":
|
||||
# validate supplied JWT on the DECORT controller
|
||||
self.validate_jwt() # this call will abort the script if validation fails
|
||||
jwt_decoded = jwt.decode(self.jwt, verify=False)
|
||||
jwt_decoded = jwt.decode(self.jwt, algorithms=["ES384"], options={"verify_signature": False})
|
||||
self.decort_username = jwt_decoded['username'] + "@" + jwt_decoded['iss']
|
||||
elif self.authenticator == "legacy":
|
||||
# obtain session id from the DECORT controller and thus validate the the legacy user
|
||||
@@ -161,7 +162,7 @@ class DecortController(object):
|
||||
# obtain JWT from Oauth2 provider and validate on the DECORT controller
|
||||
self.obtain_oauth2_jwt()
|
||||
self.validate_jwt() # this call will abort the script if validation fails
|
||||
jwt_decoded = jwt.decode(self.jwt, verify=False)
|
||||
jwt_decoded = jwt.decode(self.jwt, algorithms=["ES384"], options={"verify_signature": False})
|
||||
self.decort_username = jwt_decoded['username'] + "@" + jwt_decoded['iss']
|
||||
|
||||
# self.run_phase = "Initializing DecortController instance complete."
|
||||
@@ -272,7 +273,7 @@ class DecortController(object):
|
||||
# never be executed
|
||||
return False
|
||||
|
||||
req_url = self.controller_url + "/restmachine/cloudapi/accounts/list"
|
||||
req_url = self.controller_url + "/restmachine/cloudapi/account/list"
|
||||
req_header = dict(Authorization="bearer {}".format(arg_jwt), )
|
||||
|
||||
try:
|
||||
@@ -316,7 +317,7 @@ class DecortController(object):
|
||||
self.amodule.fail_json(**self.result)
|
||||
return False
|
||||
|
||||
req_url = self.controller_url + "/restmachine/cloudapi/users/authenticate"
|
||||
req_url = self.controller_url + "/restmachine/cloudapi/user/authenticate"
|
||||
req_data = dict(username=self.user,
|
||||
password=self.password, )
|
||||
|
||||
@@ -388,7 +389,8 @@ class DecortController(object):
|
||||
return None # actually, this directive will never be executed as fail_json aborts the script
|
||||
except requests.exceptions.Timeout:
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = "Timeout when trying to connect to '{}' when calling DECORT API.".format(api_resp.url)
|
||||
self.result['msg'] = "Timeout when trying to connect to '{}' when calling DECORT API.".format(
|
||||
api_resp.url)
|
||||
self.amodule.fail_json(**self.result)
|
||||
return None
|
||||
|
||||
@@ -518,8 +520,11 @@ class DecortController(object):
|
||||
#
|
||||
# then all values when entering this method will be of type string. We need to
|
||||
# explicitly cast int type on all of them.
|
||||
if new_data_disks is not None:
|
||||
for idx, repair in enumerate(new_data_disks):
|
||||
new_data_disks[idx] = int(repair)
|
||||
else:
|
||||
new_data_disks = []
|
||||
|
||||
for disk in comp_dict['disks']:
|
||||
if disk['type'] == 'B':
|
||||
@@ -602,7 +607,6 @@ class DecortController(object):
|
||||
|
||||
return ret_comp_id, ret_comp_dict, ret_rg_id
|
||||
|
||||
|
||||
def compute_find(self, comp_id,
|
||||
comp_name="", rg_id=0,
|
||||
check_state=True):
|
||||
@@ -649,7 +653,8 @@ class DecortController(object):
|
||||
# Therefore, RG ID cannot be zero and compute name cannot be empty.
|
||||
if not rg_id and comp_name == "":
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = "compute_find(): cannot find Compute by name when either name is empty or RG ID is zero."
|
||||
self.result[
|
||||
'msg'] = "compute_find(): cannot find Compute by name when either name is empty or RG ID is zero."
|
||||
self.amodule.fail_json(**self.result)
|
||||
# fail the module - exit
|
||||
|
||||
@@ -766,8 +771,8 @@ class DecortController(object):
|
||||
|
||||
@param (int) rg_id: ID of the RG where the VM will be provisioned.
|
||||
@param (string) comp_name: that specifies the name of the VM.
|
||||
@param (string) arch: hardware architecture of KVM VM. Supported values are: "KVM_X86" for Intel x86
|
||||
and "KVM_PPC" for IBM PowerPC.
|
||||
@param (string) arch: hardware architecture of KVM VM. Supported values are: "X86_64" for Intel x86
|
||||
and "PPC64_LE" for IBM PowerPC.
|
||||
@param (int) cpu: how many virtual CPUs to allocate.
|
||||
@param (int) ram: volume of RAM in MB to allocate (i.e. pass 4096 to allocate 4GB RAM).
|
||||
@param (int) boot_disk: boot disk size in GB.
|
||||
@@ -789,9 +794,9 @@ class DecortController(object):
|
||||
return 0
|
||||
|
||||
api_url = ""
|
||||
if arch == "KVM_X86":
|
||||
if arch == "X86_64":
|
||||
api_url = "/restmachine/cloudapi/kvmx86/create"
|
||||
elif arch == "KVM_PPC":
|
||||
elif arch == "PPC64_LE":
|
||||
api_url = "/restmachine/cloudapi/kvmppc/create"
|
||||
else:
|
||||
self.result['failed'] = True
|
||||
@@ -864,19 +869,29 @@ class DecortController(object):
|
||||
api_params = dict(accountId=comp_dict['accountId'])
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/vins/search", api_params)
|
||||
vins_list = json.loads(api_resp.content.decode('utf8'))
|
||||
if not len(vins_list):
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = ("compute_networks() cannot obtain VINS list for Account ID {}, "
|
||||
"Compute ID {}.").format(comp_dict['accountId'], comp_dict['id'])
|
||||
return
|
||||
#
|
||||
# We should not fail the module if ViNS list is empty - it is not an error, as in case of
|
||||
# API failure "decort_api_call" will abort the module execution on its own. Hence the
|
||||
# following code fragment is commented out
|
||||
#
|
||||
# if not len(vins_list):
|
||||
# self.result['failed'] = True
|
||||
# self.result['msg'] = ("compute_networks() cannot obtain VINS list for Account ID {}, "
|
||||
# "Compute ID {}.").format(comp_dict['accountId'], comp_dict['id'])
|
||||
# return
|
||||
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/externalnetwork/list", api_params)
|
||||
extnet_list = json.loads(api_resp.content.decode('utf8')) # list of dicts: "name" holds "NET_ADDR/NETMASK", "id" is ID
|
||||
if not len(vins_list):
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = ("compute_networks() cannot obtain External networks list for Account ID {}, "
|
||||
"Compute ID {}.").format(comp_dict['accountId'], comp_dict['id'])
|
||||
return
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/extnet/list", api_params)
|
||||
extnet_list = json.loads(
|
||||
api_resp.content.decode('utf8')) # list of dicts: "name" holds "NET_ADDR/NETMASK", "id" is ID
|
||||
#
|
||||
# Empty extnet_list does not constitute error condition, so we should not fail the module in
|
||||
# this case. Therefore the following code fragment is commented out.
|
||||
#
|
||||
# if not len(extnet_list):
|
||||
# self.result['failed'] = True
|
||||
# self.result['msg'] = ("compute_networks() cannot obtain External networks list for Account ID {}, "
|
||||
# "Compute ID {}.").format(comp_dict['accountId'], comp_dict['id'])
|
||||
# return
|
||||
|
||||
# Prepare the lists of network interfaces for the compute instance:
|
||||
vins_iface_list = [] # will contain dict(id=<int>, ipAddress=<str>, mac=<str>) for ifaces connected to ViNS(es)
|
||||
@@ -902,6 +917,19 @@ class DecortController(object):
|
||||
mac=iface['mac'])
|
||||
enet_iface_list.append(iface_data)
|
||||
|
||||
# If at this point compt_dict["interfaces"] lists some interfaces, but neither vins_iface_list
|
||||
# nor enet_iface_list contain any members, it means that none of the ViNS or Ext Nets currently
|
||||
# available to us match existing interfaces of the Compute instance.
|
||||
# This is abnormal condition and we should not proceed any further.
|
||||
if len(comp_dict['interfaces']) and (not len(vins_iface_list) and not len(enet_iface_list)):
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = ("compute_networks() no match between {} interface(s) of Compute ID {}"
|
||||
"and available {} ViNS(es) or {} ExtNet(s).").format(len(comp_dict['interfaces']),
|
||||
comp_dict['id'],
|
||||
len(vins_list),
|
||||
len(extnet_list))
|
||||
return
|
||||
|
||||
vins_id_list = [rec['id'] for rec in vins_iface_list]
|
||||
|
||||
enet_id_list = [rec['id'] for rec in enet_iface_list]
|
||||
@@ -1165,17 +1193,16 @@ class DecortController(object):
|
||||
# OS image manipulation methods
|
||||
###################################
|
||||
def _image_get_by_id(self, image_id):
|
||||
# TODO: update once cloudapi/images/get is implemented, see ticket #2963
|
||||
# TODO: update once cloudapi/image/get is implemented, see ticket #2963
|
||||
|
||||
api_params = dict(imageId=image_id,
|
||||
showAll=False)
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/images/get", api_params)
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/get", api_params)
|
||||
# On success the above call will return here. On error it will abort execution by calling fail_json.
|
||||
ret_image_dict = json.loads(api_resp.content.decode('utf8'))
|
||||
|
||||
return image_id, ret_image_dict
|
||||
|
||||
|
||||
def image_find(self, image_id, image_name, account_id, rg_id=0, sepid=0, pool=""):
|
||||
"""Locates image specified by name and returns its facts as dictionary.
|
||||
Primary use of this function is to obtain the ID of the image identified by its name and,
|
||||
@@ -1204,7 +1231,7 @@ class DecortController(object):
|
||||
if image_id > 0:
|
||||
ret_image_id, ret_image_dict = self._image_get_by_id(image_id)
|
||||
if (ret_image_id and
|
||||
(sepid == 0 or sepid == ret_image_dict['sepid']) and
|
||||
(sepid == 0 or sepid == ret_image_dict['sepId']) and
|
||||
(pool == "" or pool == ret_image_dict['pool'])):
|
||||
return ret_image_id, ret_image_dict
|
||||
else:
|
||||
@@ -1218,7 +1245,7 @@ class DecortController(object):
|
||||
validated_acc_id = rg_facts['accountId']
|
||||
|
||||
api_params = dict(accountId=validated_acc_id)
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/images/list", api_params)
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/list", api_params)
|
||||
# On success the above call will return here. On error it will abort execution by calling fail_json.
|
||||
images_list = json.loads(api_resp.content.decode('utf8'))
|
||||
for image_record in images_list:
|
||||
@@ -1228,7 +1255,7 @@ class DecortController(object):
|
||||
return image_record['id'], image_record
|
||||
# if positive SEP ID and/or non-emtpy pool name are passed, match by them
|
||||
full_match = True
|
||||
if sepid > 0 and sepid != image_record['sepid']:
|
||||
if sepid > 0 and sepid != image_record['sepId']:
|
||||
full_match = False
|
||||
if pool != "" and pool != image_record['pool']:
|
||||
full_match = False
|
||||
@@ -1355,12 +1382,12 @@ class DecortController(object):
|
||||
self.amodule.fail_json(**self.result)
|
||||
# try to locate RG by name - start with getting all RGs IDs within the specified account
|
||||
api_params['accountId'] = arg_account_id
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/accounts/get", api_params)
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/listRG", api_params)
|
||||
if api_resp.status_code == 200:
|
||||
account_specs = json.loads(api_resp.content.decode('utf8'))
|
||||
api_params.pop('accountId')
|
||||
for rg_id in account_specs['rgs']:
|
||||
got_id, got_specs = self._rg_get_by_id(rg_id)
|
||||
for rg_item in account_specs:
|
||||
got_id, got_specs = self._rg_get_by_id(rg_item['id'])
|
||||
if got_id and got_specs['name'] == arg_rg_name:
|
||||
# name matches
|
||||
if not arg_check_state or got_specs['status'] not in RG_INVALID_STATES:
|
||||
@@ -1377,7 +1404,6 @@ class DecortController(object):
|
||||
|
||||
return ret_rg_id, ret_rg_dict
|
||||
|
||||
|
||||
def rg_provision(self, arg_account_id, arg_rg_name, arg_username, arg_quota={}, arg_location="", arg_desc=""):
|
||||
"""Provision new RG according to the specified arguments.
|
||||
If critical error occurs the embedded call to API function will abort further execution of the script
|
||||
@@ -1409,7 +1435,8 @@ class DecortController(object):
|
||||
target_gid = self.gid_get(arg_location)
|
||||
if not target_gid:
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = ("rg_provision() failed to obtain valid Grid ID for location '{}'").format(arg_location)
|
||||
self.result['msg'] = ("rg_provision() failed to obtain valid Grid ID for location '{}'").format(
|
||||
arg_location)
|
||||
self.amodule.fail_json(**self.result)
|
||||
|
||||
api_params = dict(accountId=arg_account_id,
|
||||
@@ -1535,7 +1562,8 @@ class DecortController(object):
|
||||
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_state")
|
||||
|
||||
NOP_STATES_FOR_RG_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING", "DESTROYED"]
|
||||
NOP_STATES_FOR_RG_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING",
|
||||
"DESTROYED"]
|
||||
VALID_TARGET_STATES = ["enabled", "disabled"]
|
||||
|
||||
if arg_rg_dict['status'] in NOP_STATES_FOR_RG_CHANGE:
|
||||
@@ -1608,12 +1636,12 @@ class DecortController(object):
|
||||
|
||||
if account_name == "":
|
||||
api_params['accountId'] = account_id
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/accounts/get", api_params)
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/get", api_params)
|
||||
if api_resp.status_code == 200:
|
||||
account_details = json.loads(api_resp.content.decode('utf8'))
|
||||
return account_details['id'], account_details
|
||||
else:
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/accounts/list", api_params)
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/list", api_params)
|
||||
if api_resp.status_code == 200:
|
||||
# Parse response to see if a account matching arg_account_name is found in the output
|
||||
# If it is found, assign its ID to the return variable and copy dictionary with the facts
|
||||
@@ -1623,7 +1651,7 @@ class DecortController(object):
|
||||
# get detailed information about the account from "accounts/get" call as
|
||||
# "accounts/list" does not return all necessary fields
|
||||
api_params['accountId'] = runner['id']
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/accounts/get", api_params)
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/get", api_params)
|
||||
if api_resp.status_code == 200:
|
||||
account_details = json.loads(api_resp.content.decode('utf8'))
|
||||
return account_details['id'], account_details
|
||||
@@ -2047,7 +2075,8 @@ class DecortController(object):
|
||||
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_state")
|
||||
|
||||
NOP_STATES_FOR_VINS_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING", "DESTROYED"]
|
||||
NOP_STATES_FOR_VINS_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING",
|
||||
"DESTROYED"]
|
||||
VALID_TARGET_STATES = ["enabled", "disabled"]
|
||||
|
||||
if vins_dict['status'] in NOP_STATES_FOR_VINS_CHANGE:
|
||||
@@ -2122,7 +2151,7 @@ class DecortController(object):
|
||||
"was requested.").format(vins_dict['id'], vins_dict['name'])
|
||||
return
|
||||
|
||||
if not vins_dict['rgid']:
|
||||
if not vins_dict['rgId']:
|
||||
# this ViNS exists at account level - no updates are possible
|
||||
self.result['warning'] = ("vins_update(): no update is possible for ViNS ID {} "
|
||||
"as it exists at account level.").format(vins_dict['id'])
|
||||
@@ -2170,7 +2199,8 @@ class DecortController(object):
|
||||
self.result['failed'] = False
|
||||
self.result['warning'] = ("vins_update(): ViNS ID {} is already connected to ext net ID {}, "
|
||||
"no reconnection to default network will be done.").format(vins_dict['id'],
|
||||
gw_config['ext_net_id'])
|
||||
gw_config[
|
||||
'ext_net_id'])
|
||||
|
||||
return
|
||||
|
||||
@@ -2260,7 +2290,8 @@ class DecortController(object):
|
||||
|
||||
if self.amodule.check_mode:
|
||||
self.result['failed'] = False
|
||||
self.result['msg'] = "disk_find() in check mode: find Disk ID {} / name '{}' was requested.".format(disk_id, disk_name)
|
||||
self.result['msg'] = "disk_find() in check mode: find Disk ID {} / name '{}' was requested.".format(disk_id,
|
||||
disk_name)
|
||||
return
|
||||
|
||||
ret_disk_id = 0
|
||||
@@ -2301,7 +2332,7 @@ class DecortController(object):
|
||||
|
||||
return 0, None
|
||||
|
||||
def disk_provision(self, disk_name, size, account_id, sep_id, pool="default", desc="", location=""):
|
||||
def disk_provision(self, disk_name, size, account_id, sep_id, pool_name="", desc="", location=""):
|
||||
"""Provision Disk according to the specified arguments.
|
||||
Note that disks created by this method will be of type 'D' (data disks).
|
||||
If critical error occurs the embedded call to API function will abort further execution
|
||||
@@ -2323,7 +2354,8 @@ class DecortController(object):
|
||||
|
||||
if self.amodule.check_mode:
|
||||
self.result['failed'] = False
|
||||
self.result['msg'] = "disk_provision() in check mode: create Disk name '{}' was requested.".format(disk_name)
|
||||
self.result['msg'] = "disk_provision() in check mode: create Disk name '{}' was requested.".format(
|
||||
disk_name)
|
||||
return 0
|
||||
|
||||
target_gid = self.gid_get(location)
|
||||
@@ -2336,11 +2368,12 @@ class DecortController(object):
|
||||
api_params = dict(accountId=account_id,
|
||||
gid=target_gid,
|
||||
name=disk_name,
|
||||
description=desc,
|
||||
desc=desc,
|
||||
size=size,
|
||||
type='D',
|
||||
sep_id=sep_id,
|
||||
pool=pool,)
|
||||
sepId=sep_id, )
|
||||
if pool_name != "":
|
||||
api_params['pool'] = pool_name
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/create", api_params)
|
||||
if api_resp.status_code == 200:
|
||||
ret_disk_id = json.loads(api_resp.content.decode('utf8'))
|
||||
@@ -2374,7 +2407,8 @@ class DecortController(object):
|
||||
|
||||
if not new_size:
|
||||
self.result['failed'] = False
|
||||
self.result['warning'] = "disk_resize(): zero size requested for Disk ID {} - ignoring.".format(disk_facts['id'])
|
||||
self.result['warning'] = "disk_resize(): zero size requested for Disk ID {} - ignoring.".format(
|
||||
disk_facts['id'])
|
||||
return
|
||||
|
||||
if new_size < disk_facts['sizeMax']:
|
||||
@@ -2425,7 +2459,6 @@ class DecortController(object):
|
||||
self.result['changed'] = True
|
||||
return
|
||||
|
||||
|
||||
##############################
|
||||
#
|
||||
# Port Forward rules management
|
||||
@@ -2500,7 +2533,8 @@ class DecortController(object):
|
||||
break
|
||||
else:
|
||||
decon.result['failed'] = True
|
||||
decon.result['msg'] = "Compute ID {} is not connected to ViNS ID {}.".format(comp_facts['id'], vins_facts['id'])
|
||||
decon.result['msg'] = "Compute ID {} is not connected to ViNS ID {}.".format(comp_facts['id'],
|
||||
vins_facts['id'])
|
||||
return ret_rules
|
||||
|
||||
existing_rules = []
|
||||
@@ -2615,3 +2649,256 @@ class DecortController(object):
|
||||
|
||||
ret_rules = self._pfw_get(comp_facts['id'], vins_facts['id'])
|
||||
return ret_rules
|
||||
|
||||
def _k8s_get_by_id(self, k8s_id):
|
||||
"""Helper function that locates k8s by ID and returns k8s facts.
|
||||
|
||||
@param (int) k8s_id: ID of the k8s to find and return facts for.
|
||||
|
||||
@return: k8s ID and a dictionary of k8s facts as provided by k8s/get API call. Note that if it fails
|
||||
to find the k8s with the specified ID, it may return 0 for ID and empty dictionary for the facts. So
|
||||
it is suggested to check the return values accordingly.
|
||||
"""
|
||||
ret_k8s_id = 0
|
||||
ret_k8s_dict = dict()
|
||||
|
||||
if not k8s_id:
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = "k8s_get_by_id(): zero k8s ID specified."
|
||||
self.amodule.fail_json(**self.result)
|
||||
|
||||
api_params = dict(k8sId=k8s_id, )
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/get", api_params)
|
||||
if api_resp.status_code == 200:
|
||||
ret_k8s_id = k8s_id
|
||||
ret_k8s_dict = json.loads(api_resp.content.decode('utf8'))
|
||||
else:
|
||||
self.result['warning'] = ("k8s_get_by_id(): failed to get k8s by ID {}. HTTP code {}, "
|
||||
"response {}.").format(k8s_id, api_resp.status_code, api_resp.reason)
|
||||
|
||||
return ret_k8s_id, ret_k8s_dict
|
||||
|
||||
def k8s_find(self, arg_k8s_id=0, arg_k8s_name="", arg_check_state=True):
|
||||
"""Returns non zero k8s ID and a dictionary with k8s details on success, 0 and empty dictionary otherwise.
|
||||
This method does not fail the run if k8s cannot be located by its name (arg_k8s_name), because this could be
|
||||
an indicator of the requested k8s never existed before.
|
||||
However, it does fail the run if k8s cannot be located by arg_k8s_id (if non zero specified) or if API errors
|
||||
occur.
|
||||
@param (int) arg_k8s_id: integer ID of the k8s to be found. If non-zero k8s ID is passed, account ID and k8s name
|
||||
are ignored. However, k8s must be present in this case, as knowing its ID implies it already exists, otherwise
|
||||
method will fail.
|
||||
@param (string) arg_k8s_name: string that defines the name of k8s to be found. This parameter is case sensitive.
|
||||
@param (bool) arg_check_state: tells the method to report k8s in valid states only.
|
||||
|
||||
@return: ID of the k8s, if found. Zero otherwise.
|
||||
@return: dictionary with k8s facts if k8s is present. Empty dictionary otherwise. None on error.
|
||||
"""
|
||||
|
||||
# Resource group can be in one of the following states:
|
||||
# MODELED, CREATED, DISABLING, DISABLED, ENABLING, DELETING, DELETED, DESTROYED, DESTROYED
|
||||
#
|
||||
# Transient state (ending with ING) are invalid from k8s manipulation viewpoint
|
||||
#
|
||||
|
||||
K8S_INVALID_STATES = ["MODELED"]
|
||||
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_find")
|
||||
|
||||
ret_k8s_id = 0
|
||||
api_params = dict(includedeleted=True)
|
||||
ret_k8s_dict = None
|
||||
|
||||
if arg_k8s_id > 0:
|
||||
ret_k8s_id, ret_k8s_dict = self._k8s_get_by_id(arg_k8s_id)
|
||||
if not ret_k8s_id:
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = "k8s_find(): cannot find k8s by ID {}.".format(arg_k8s_id)
|
||||
self.amodule.fail_json(**self.result)
|
||||
elif arg_k8s_name != "":
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/list", api_params)
|
||||
if api_resp.status_code == 200:
|
||||
account_specs = json.loads(api_resp.content.decode('utf8'))
|
||||
for k8s_item in account_specs:
|
||||
got_id, got_specs = self._k8s_get_by_id(k8s_item['id'])
|
||||
if got_id and got_specs['name'] == arg_k8s_name:
|
||||
# name matches
|
||||
if not arg_check_state or got_specs['status'] not in K8S_INVALID_STATES:
|
||||
ret_k8s_id = got_id
|
||||
ret_k8s_dict = got_specs
|
||||
break
|
||||
# Note: we do not fail the run if k8s cannot be located by its name, because it could be a new k8s
|
||||
# that never existed before. In this case ret_k8s_id=0 and empty ret_k8s_dict will be returned.
|
||||
else:
|
||||
# Both arg_k8s_id and arg_k8s_name are empty - there is no way to locate k8s in this case
|
||||
self.result['failed'] = True
|
||||
self.result['msg'] = "k8s_find(): either non-zero ID or a non-empty name must be specified."
|
||||
self.amodule.fail_json(**self.result)
|
||||
|
||||
return ret_k8s_id, ret_k8s_dict
|
||||
|
||||
def k8s_state(self, arg_k8s_dict, arg_desired_state, arg_started=False):
|
||||
"""Enable or disable k8s cluster.
|
||||
|
||||
@param arg_k8s_dict: dictionary with the target k8s facts as returned by k8s_find(...) method or
|
||||
.../k8s/get API call.
|
||||
@param arg_desired_state: the desired state for this k8s cluster. Valid states are 'enabled' and 'disabled'.
|
||||
@param arg_started: the desired tech state for this k8s cluster. Valid states are 'True' and 'False'.
|
||||
"""
|
||||
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_state")
|
||||
|
||||
NOP_STATES_FOR_K8S_CHANGE = ["MODELED", "DISABLING",
|
||||
"ENABLING", "DELETING",
|
||||
"DELETED", "DESTROYING",
|
||||
"DESTROYED", "CREATING",
|
||||
"RESTORING"]
|
||||
VALID_TARGET_STATES = ["ENABLED", "DISABLED"]
|
||||
|
||||
if arg_k8s_dict['status'] in NOP_STATES_FOR_K8S_CHANGE:
|
||||
self.result['failed'] = False
|
||||
self.result['msg'] = ("k8s_state(): no state change possible for k8s ID {} "
|
||||
"in its current state '{}'.").format(arg_k8s_dict['id'], arg_k8s_dict['status'])
|
||||
return
|
||||
if arg_k8s_dict['status'] not in VALID_TARGET_STATES:
|
||||
self.result['failed'] = False
|
||||
self.result['warning'] = ("k8s_state(): unrecognized desired state '{}' requested "
|
||||
"for k8s ID {}. No k8s state change will be done.").format(arg_desired_state,
|
||||
arg_k8s_dict['id'])
|
||||
return
|
||||
|
||||
if self.amodule.check_mode:
|
||||
self.result['failed'] = False
|
||||
self.result['msg'] = ("k8s_state() in check mode: setting state of k8s ID {}, name '{}' to "
|
||||
"'{}' was requested.").format(arg_k8s_dict['id'], arg_k8s_dict['name'],
|
||||
arg_desired_state)
|
||||
return
|
||||
|
||||
k8s_state_api = "" # This string will also be used as a flag to indicate that API call is necessary
|
||||
api_params = dict(k8sId=arg_k8s_dict['id'])
|
||||
expected_state = ""
|
||||
tech_state = ""
|
||||
if arg_k8s_dict['status'] in ["CREATED", "ENABLED"] and arg_desired_state == 'disabled':
|
||||
k8s_state_api = "/restmachine/cloudapi/k8s/disable"
|
||||
expected_state = "DISABLED"
|
||||
elif arg_k8s_dict['status'] in ["CREATED", "DISABLED"] and arg_desired_state == 'enabled':
|
||||
k8s_state_api = "/restmachine/cloudapi/k8s/enable"
|
||||
expected_state = "ENABLED"
|
||||
elif arg_k8s_dict['status'] == "ENABLED" and arg_desired_state == 'enabled' and arg_started is True and arg_k8s_dict['techStatus'] == "STOPPED":
|
||||
k8s_state_api = "/restmachine/cloudapi/k8s/start"
|
||||
tech_state = "STARTED"
|
||||
elif arg_k8s_dict['status'] == "ENABLED" and arg_desired_state == 'enabled' and arg_started is False and arg_k8s_dict['techStatus'] == "STARTED":
|
||||
k8s_state_api = "/restmachine/cloudapi/k8s/stop"
|
||||
tech_state = "STOPPED"
|
||||
if k8s_state_api != "":
|
||||
self.decort_api_call(requests.post, k8s_state_api, api_params)
|
||||
# On success the above call will return here. On error it will abort execution by calling fail_json.
|
||||
self.result['failed'] = False
|
||||
self.result['changed'] = True
|
||||
arg_k8s_dict['status'] = expected_state
|
||||
arg_k8s_dict['started'] = tech_state
|
||||
else:
|
||||
self.result['failed'] = False
|
||||
self.result['msg'] = ("k8s_state(): no state change required for k8s ID {} from current "
|
||||
"state '{}' to desired state '{}'.").format(arg_k8s_dict['id'],
|
||||
arg_k8s_dict['status'],
|
||||
arg_desired_state)
|
||||
return
|
||||
def k8s_delete(self, k8s_id, permanently=False):
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_delete")
|
||||
|
||||
if self.amodule.check_mode:
|
||||
self.result['failed'] = False
|
||||
self.result['msg'] = "k8s_delete() in check mode: delete Compute ID {} was requested.".format(k8s_id)
|
||||
return
|
||||
|
||||
api_params = dict(k8sId=k8s_id,
|
||||
permanently=permanently,
|
||||
)
|
||||
self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/delete", api_params)
|
||||
# On success the above call will return here. On error it will abort execution by calling fail_json.
|
||||
self.result['failed'] = False
|
||||
self.result['changed'] = True
|
||||
return
|
||||
def k8s_restore(self, k8s_id ):
|
||||
"""Restores a deleted k8s cluster identified by ID.
|
||||
|
||||
@param k8s_id: ID of the k8s cluster to restore.
|
||||
"""
|
||||
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_restore")
|
||||
|
||||
if self.amodule.check_mode:
|
||||
self.result['failed'] = False
|
||||
self.result['msg'] = "k8s_restore() in check mode: restore k8s ID {} was requested.".format(k8s_id)
|
||||
return
|
||||
|
||||
api_params = dict(k8sId=k8s_id)
|
||||
self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/restore", api_params)
|
||||
# On success the above call will return here. On error it will abort execution by calling fail_json.
|
||||
self.result['failed'] = False
|
||||
self.result['changed'] = True
|
||||
return
|
||||
def k8s_provision(self, k8s_name,
|
||||
wg_name, k8ci_id,
|
||||
rg_id, master_count,
|
||||
master_cpu, master_ram,
|
||||
master_disk, worker_count,
|
||||
worker_cpu, worker_ram,
|
||||
worker_disk, extnet_id,
|
||||
with_lb, annotation, ):
|
||||
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_provision")
|
||||
|
||||
if self.amodule.check_mode:
|
||||
self.result['failed'] = False
|
||||
self.result['msg'] = ("k8s_provision() in check mode. Provision k8s '{}' in RG ID {} "
|
||||
"was requested.").format(k8s_name, rg_id)
|
||||
return 0
|
||||
|
||||
api_url = "/restmachine/cloudapi/k8s/create"
|
||||
api_params = dict(name=k8s_name,
|
||||
rgId=rg_id,
|
||||
k8ciId=k8ci_id,
|
||||
workerGroupName=wg_name,
|
||||
masterNum=master_count,
|
||||
masterCpu=master_cpu,
|
||||
masterRam=master_ram,
|
||||
masterDisk=master_disk,
|
||||
workerNum=worker_count,
|
||||
workerCpu=worker_cpu,
|
||||
workerRam=worker_ram,
|
||||
workerDisk=worker_disk,
|
||||
extnetId=extnet_id,
|
||||
withLB=with_lb,
|
||||
desc=annotation,
|
||||
)
|
||||
api_resp = self.decort_api_call(requests.post, api_url, api_params)
|
||||
k8s_id = ""
|
||||
if api_resp.status_code == 200:
|
||||
for i in range(300):
|
||||
api_get_url = "/restmachine/cloudapi/tasks/get"
|
||||
api_get_params = dict(
|
||||
auditId=api_resp.content.decode('utf8').replace('"', '')
|
||||
)
|
||||
api_get_resp = self.decort_api_call(requests.post, api_get_url, api_get_params)
|
||||
ret_info = json.loads(api_get_resp.content.decode('utf8'))
|
||||
if api_get_resp.status_code == 200:
|
||||
if ret_info['status'] in ["PROCESSING", "SCHEDULED"]:
|
||||
self.result['failed'] = False
|
||||
time.sleep(30)
|
||||
elif ret_info['status'] == "ERROR":
|
||||
self.result['failed'] = True
|
||||
return
|
||||
elif ret_info['status'] == "OK":
|
||||
k8s_id = ret_info['result'][0]
|
||||
self.result['changed'] = True
|
||||
return k8s_id
|
||||
else:
|
||||
k8s_id = ret_info['status']
|
||||
else:
|
||||
self.result['failed'] = True
|
||||
# Timeout
|
||||
self.result['failed'] = True
|
||||
else:
|
||||
self.result['failed'] = True
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user