2 Commits

13 changed files with 1775 additions and 226 deletions

40
examples/VINS.yaml Normal file
View File

@@ -0,0 +1,40 @@
---
#
# DECORT vins module example
#
- hosts: localhost
tasks:
- name: obtain JWT
decort_jwt:
oauth2_url: "https://sso.digitalenergy.online"
validity: 1200
register: my_jwt
delegate_to: localhost
- name: print out JWT
debug:
var: my_jwt.jwt
delegate_to: localhost
- name: Manage ViNS at resource group level
decort_vins:
authenticator: jwt
jwt: "{{ my_jwt.jwt }}"
controller_url: "https://ds1.digitalenergy.online"
vins_name: "vins_created_by_decort_VINS_module"
state: present
rg_id: 198
ext_net_id: -1
ipcidr: "10.20.30.0/24"
mgmtaddr: "10.20.30.1"
custom_config: false
config_save: false
verify_ssl: false
register: managed_vins
- name: print VINS facter
debug:
msg: "{{managed_vins.facts.password}}"
when: managed_vins.facts.password is defined

36
examples/affinity.yaml Normal file
View File

@@ -0,0 +1,36 @@
---
#
# DECORT kvmvm module example
#
- hosts: ansible_master
tasks:
- name: create a VM named cloud-init_example
decort_kvmvm:
name: affinity_example
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "" # Application id from SSO Digital Energy
app_secret: "" # API key from SSO Digital Energy
controller_url: "" #"https://mr4.digitalenergy.online"
rg_id: # Resource group id
cpu: 2
ram: 2048
boot_disk: 10
image_name: "DECS Ubuntu 18.04 v1.2.3" # Name of OS image
networks:
- type: VINS
id: # VINS id
tags: "Ansible cloud init example"
aff_lable: "Affinity lable"
tag:
- key: bd
value: main
aff_rule:
- key: app
value: main
topology: compute
policy: REQUIRED
mode: EQ
state: present
delegate_to: localhost
register: simple_vm

40
examples/annotations.yaml Normal file
View File

@@ -0,0 +1,40 @@
---
#
# DECORT k8s module labels, taints, annotations example
#
- hosts: localhost
tasks:
- name: obtain JWT
decort_jwt:
oauth2_url: "https://sso.digitalenergy.online"
validity: 1200
register: my_jwt
delegate_to: localhost
- name: print out JWT
debug:
var: my_jwt.jwt
delegate_to: localhost
- name: Create k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ my_jwt.jwt }}"
controller_url: "https://mr4.digitalenergy.online"
name: "example_kubernetes"
rg_id: 199
k8ci_id: 4
state: present
workers:
- name: workgroup1
labels:
- disktype1=ssd1
- disktype2=ssd2
taints:
- key1=value1:NoSchedule
- key2=value2:NoSchedule
annotations:
- node.deckhouse.io/group1=g1
- node.deckhouse.io/group2=g2
register: kube

View File

@@ -0,0 +1,36 @@
---
#
# DECORT kvmvm module example
#
- hosts: ansible_master
tasks:
- name: create a VM named cloud-init_example
decort_kvmvm:
name: anti-affinity_example
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "" # Application id from SSO Digital Energy
app_secret: "" # API key from SSO Digital Energy
controller_url: "" #"https://mr4.digitalenergy.online"
rg_id: # Resource group id
cpu: 2
ram: 2048
boot_disk: 10
image_name: "DECS Ubuntu 18.04 v1.2.3" #Name of OS image
networks:
- type: VINS
id: #VINS id
tags: "Ansible cloud init example"
aff_lable: "Anti affinity lable"
tag:
- key: bd
value: main
aaff_rule:
- key: app
value: main
topology: compute
policy: REQUIRED
mode: ANY
state: present
delegate_to: localhost
register: simple_vm

View File

@@ -0,0 +1,31 @@
---
#
# DECORT vins module example
#
- hosts: localhost
tasks:
- name: obtain JWT
decort_jwt:
oauth2_url: "https://sso.digitalenergy.online"
validity: 1200
register: my_jwt
delegate_to: localhost
- name: print out JWT
debug:
var: my_jwt.jwt
delegate_to: localhost
- name: Manage bservice at RG
decort_bservice:
account_id: 98
verify_ssl: false
authenticator: jwt
jwt: "{{ my_jwt.jwt }}"
controller_url: "https://ds1.digitalenergy.online"
rg_id: 1629
state: present
name: databases
started: True
register: db_bservice

38
examples/cloud_init.yaml Normal file
View File

@@ -0,0 +1,38 @@
#
# DECORT kvmvm module example
#
- hosts: ansible_master
tasks:
- name: create a VM named cloud-init_example
decort_kvmvm:
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "" # Application id from SSO Digital Energy
app_secret: "" # API key from SSO Digital Energy
controller_url: "" #"https://mr4.digitalenergy.online"
name: cloud-init_example
cpu: 2
ram: 2048
boot_disk: 10
image_name: "DECS Ubuntu 18.04 v1.2.3" #Name of OS image
networks:
- type: VINS
id: #VINS id
tags: "Ansible cloud init example"
state: present
rg_id: #Resource group id
ci_user_data:
- packages:
- apache2
- write_files:
- content: |
<div>
Hello World!
</div>
owner: user:user
path: /var/www/html/index.html
- hostname: test-apache
- ssh_keys:
- rsa_public: ssh-rsa AAAAOasDmLxnD= user@pc
delegate_to: localhost
register: simple_vm

39
examples/kubernetes.yaml Normal file
View File

@@ -0,0 +1,39 @@
---
#
# DECORT k8s module example
#
- hosts: ansible_master
tasks:
- name: obtain JWT
decort_jwt:
oauth2_url: "" #"https://sso.digitalenergy.online"
validity: 1200
verify_ssl: false
register: token
delegate_to: localhost
- name: create a VM named cloud-init_example
decort_k8s:
state: present
started: True
getConfig: True
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "" #"https://mr4.digitalenergy.online"
name: "cluster-test"
rg_id: # Resource group id
k8ci_id: # k8s ci id
workers:
- name: wg1
ram: 1024
cpu: 10
disk: 10
num: 1
- name: wg2
ram: 1024
cpu: 10
disk: 10
num: 2
verify_ssl: false
delegate_to: localhost
register: kube

289
library/decort_bservice.py Normal file
View File

@@ -0,0 +1,289 @@
#!/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: Alexey Dankov (alexey Dankov@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 *
class decort_bservice(DecortController):
def __init__(self,arg_amodule):
super(decort_bservice, self).__init__(arg_amodule)
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
self.bservice_info = None
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot manage Basic Services when its ID is 0 and name is empty."
self.fail_json(**self.result)
if not arg_amodule.params['id']:
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Current user does not have access to the account ID {} / "
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
arg_amodule.params['account_name'])
self.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id,
arg_amodule.params['rg_id'],)
if not validated_rg_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name'])
self.fail_json(**self.result)
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
self.bservice_id,self.bservice_info = self.bservice_find(
self.acc_id,
validated_rg_id,
arg_amodule.params['name'],
arg_amodule.params['id']
)
if self.bservice_id == 0:
self.bservice_should_exist = False
else:
self.bservice_should_exist = True
def nop(self):
"""No operation (NOP) handler for B-service.
This function is intended to be called from the main switch construct of the module
when current state -> desired state change logic does not require any changes to
the actual Compute state.
"""
self.result['failed'] = False
self.result['changed'] = False
if self.k8s_id:
self.result['msg'] = ("No state change required for B-service ID {} because of its "
"current status '{}'.").format(self.bservice_id, self.bservice_info['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent B-service instance.").format(self.amodule.params['state'])
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.bservice_id:
self.result['msg'] = ("Invalid target state '{}' requested for B-service ID {} in the "
"current status '{}'.").format(self.bservice_id,
self.amodule.params['state'],
self.bservice_info['status'])
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent B-service name '{}' "
"in RG ID {} / name '{}'").format(self.amodule.params['state'],
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['rg_name'])
return
def create(self):
self.bservice_id = self.bservice_id = self.bservice_provision(
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['sshuser'],
self.amodule.params['sshkey']
)
if self.bservice_id:
_, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
self.bservice_state(self.bservice_info,'enabled',self.amodule.params['started'])
return
def action(self,d_state,started=False):
self.bservice_state(self.bservice_info,d_state,started)
return
def restore(self):
self.result['failed'] = True
self.result['msg'] = "Restore B-Service ID {} manualy.".format(self.bservice_id)
pass
def destroy(self):
self.bservice_delete(self.bservice_id)
self.bservice_info['status'] = 'DELETED'
self.bservice_should_exist = False
return
def package_facts(self,check_mode=False):
ret_dict = dict(
name="",
state="CHECK_MODE",
account_id=0,
rg_id=0,
config=None,
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
ret_dict['id'] = self.bservice_info['id']
ret_dict['name'] = self.bservice_info['name']
ret_dict['techStatus'] = self.bservice_info['techStatus']
ret_dict['state'] = self.bservice_info['status']
ret_dict['rg_id'] = self.bservice_info['rgId']
ret_dict['account_id'] = self.acc_id
ret_dict['groupsName'] = self.bservice_info['groupsName']
ret_dict['groupsIds'] = self.bservice_info['groups']
return ret_dict
@staticmethod
def build_parameters():
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),
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),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present','check']),
started=dict(type='bool', required=False, default=True),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
name=dict(type='str', required=True),
sshuser=dict(type='str', required=False,default=None),
sshkey=dict(type='str', required=False,default=None),
id=dict(type='int', required=False, default=0),
rg_id=dict(type='int', default=0),
rg_name=dict(type='str',default=""),
description=dict(type='str', default="Created by decort ansible module"),
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_bservice.build_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'],
],
required_one_of=[
['id', 'name'],
['rg_id','rg_name']
],
)
subj = decort_bservice(amodule)
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.bservice_id:
subj.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate B-service name '{}'. Other arguments are: B-service ID {}, "
"RG name '{}', RG ID {}, Account '{}'.").format(amodule.params['name'],
amodule.params['id'],
amodule.params['rg_name'],
amodule.params['rg_id'],
amodule.params['account_name'])
amodule.fail_json(**subj.result)
pass
#MAIN MANAGE PART
if subj.bservice_id:
if subj.bservice_info['status'] in ("DELETING","DESTROYNG","RECONFIGURING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"):
subj.error()
elif subj.bservice_info['status'] == "DELETED":
if amodule.params['state'] in ('disabled', 'enabled', 'present'):
subj.restore(subj.bservice_id)
subj.action(amodule.params['state'],amodule.params['started'])
if amodule.params['state'] == 'absent':
subj.nop()
elif subj.bservice_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'disabled':
subj.action(amodule.params['state'],amodule.params['started'])
elif amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action(amodule.params['state'],amodule.params['started'])
elif subj.bservice_info['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
subj.destroy()
elif amodule.params['state'] in ('present','enabled'):
subj.action(amodule.params['state'],amodule.params['started'])
else:
subj.nop()
elif subj.bservice_info['status'] == "DESTROED":
if amodule.params['state'] in ('present','enabled'):
subj.create()
subj.action(amodule.params['state'],amodule.params['started'])
if amodule.params['state'] == 'absent':
subj.nop()
else:
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started'):
subj.create()
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
subj.error()
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
if subj.bservice_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
else:
amodule.exit_json(**subj.result)
if __name__ == "__main__":
main()

285
library/decort_group.py Normal file
View File

@@ -0,0 +1,285 @@
#!/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: Alexey Dankov (alexey.dankov@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 *
class decort_group(DecortController):
def __init__(self,arg_amodule):
super(decort_group, self).__init__(arg_amodule)
self.group_should_exist = False
validated_bservice_id = None
#find and validate B-Service
validated_bservice_id, bservice_info = self.bservice_get_by_id(arg_amodule.params['bservice_id'])
if not validated_bservice_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Cannot find B-service ID {}.").format(arg_amodule.params['bservice_id'])
self.fail_json(**self.result)
#find group
self.bservice_id = validated_bservice_id
self.bservice_info = bservice_info
self.group_id,self.group_info = self.group_find(
bs_id=validated_bservice_id,
bs_info=bservice_info,
group_id=arg_amodule.params['id'],
group_name=arg_amodule.params['name'],
)
if self.group_id:
self.group_should_exist = True
return
def nop(self):
"""No operation (NOP) handler for B-service.
This function is intended to be called from the main switch construct of the module
when current state -> desired state change logic does not require any changes to
the actual Compute state.
"""
self.result['failed'] = False
self.result['changed'] = False
if self.group_id:
self.result['msg'] = ("No state change required for B-service ID {} because of its "
"current status '{}'.").format(self.group_id, self.group_info['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent B-service instance.").format(self.amodule.params['state'])
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.group_id:
self.result['msg'] = ("Invalid target state '{}' requested for Group ID {} in the "
"current status '{}'.").format(self.group_id,
self.amodule.params['state'],
self.group_info['status'])
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent Group name '{}' "
"in B-service {}").format(self.amodule.params['state'],
self.amodule.params['name'],
self.amodule.params['bservice_id'],
)
return
def create(self):
if self.amodule.params['driver'] not in ["KVM_X86","KVM_PPC"]:
self.result['failed'] = True
self.result['msg'] = ("Unsupported driver '{}' is specified for "
"Group.").format(self.amodule.params['driver'])
self.amodule.fail_json(**self.result)
self.group_id=self.group_provision(
self.bservice_id,
self.amodule.params['name'],
self.amodule.params['count'],
self.amodule.params['cpu'],
self.amodule.params['ram'],
self.amodule.params['boot_disk'],
self.amodule.params['image_id'],
self.amodule.params['driver'],
self.amodule.params['role'],
self.amodule.params['networks'],
self.amodule.params['timeoutStart'],
)
if self.amodule.params['state'] in ('started','present'):
self.group_state(self.bservice_id,self.group_id,self.amodule.params['state'])
return
def action(self):
#change desired state
if (
self.group_info['techStatus'] == 'STARTED' and self.amodule.params['state'] == 'stopped') or (
self.group_info['techStatus'] == 'STOPPED' and self.amodule.params['state'] in ('started','present')
):
self.group_state(self.bservice_id,self.group_id,self.amodule.params['state'])
self.group_resize_count(self.bservice_id,self.group_info,self.amodule.params['count'])
self.group_update_hw(
self.bservice_id,
self.group_info,
self.amodule.params['cpu'],
self.amodule.params['boot_disk'],
self.amodule.params['name'],
self.amodule.params['role'],
self.amodule.params['ram'],
)
self.group_update_net(
self.bservice_id,
self.group_info,
self.amodule.params['networks']
)
return
def destroy(self):
self.group_delete(
self.bservice_id,
self.group_id
)
return
def package_facts(self,check_mode=False):
ret_dict = dict(
name="",
state="CHECK_MODE",
account_id=0,
rg_id=0,
config=None,
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
if self.result['changed'] == True:
self.group_id,self.group_info = self.group_find(
self.bservice_id,
self.bservice_info,
self.group_id
)
ret_dict['account_id'] = self.group_info['accountId']
ret_dict['rg_id'] = self.group_info['rgId']
ret_dict['id'] = self.group_info['id']
ret_dict['name'] = self.group_info['name']
ret_dict['techStatus'] = self.group_info['techStatus']
ret_dict['state'] = self.group_info['status']
ret_dict['Computes'] = self.group_info['computes']
return ret_dict
@staticmethod
def build_parameters():
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),
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),
state=dict(type='str',
default='present',
choices=['absent', 'started', 'stopped', 'present','check']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
name=dict(type='str', required=True),
id=dict(type='int', required=False, default=0),
image_id=dict(type='int', required=False),
image_name=dict(type='str', required=False),
driver=dict(type='str', required=False,default="KVM_X86"),
boot_disk=dict(type='int', required=False),
bservice_id=dict(type='int', required=True),
count=dict(type='int', required=True),
timeoutStart=dict(type='int', required=False),
role=dict(type='str', required=False),
cpu=dict(type='int', required=False),
ram=dict(type='int', required=False),
networks=dict(type='list', default=[], required=False),
description=dict(type='str', default="Created by decort ansible module"),
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_group.build_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'],
],
required_one_of=[
['id', 'name'],
],
)
subj = decort_group(amodule)
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.group_id:
# cluster is found - package facts and report success to Ansible
subj.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate Group name '{}'. "
"B-service ID {}").format(amodule.params['name'],
amodule.params['bservice_id'],)
amodule.fail_json(**subj.result)
if subj.group_id:
if subj.group_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED",
"DISABLED","DESTROYED"):
subj.error()
elif subj.group_info['status'] in ("DELETED","DESTROYED"):
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started','stopped'):
subj.create()
elif subj.group_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action()
else:
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started','stopped'):
subj.create()
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
if subj.group_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
else:
amodule.exit_json(**subj.result)
if __name__ == "__main__":
main()

View File

@@ -14,105 +14,243 @@ 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 *
class decort_k8s(DecortController):
def __init__(self,arg_amodule):
super(decort_k8s, self).__init__(arg_amodule)
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.
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
validated_k8ci_id = 0
@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
"""
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot manage k8s cluster when its ID is 0 and name is empty."
self.fail_json(**self.result)
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
)
if arg_check_mode:
# in check mode return immediately with the default values
if not arg_amodule.params['id']:
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Current user does not have access to the account ID {} / "
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
arg_amodule.params['account_name'])
self.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id,
arg_amodule.params['rg_id'],)
if not validated_rg_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name'])
self.fail_json(**self.result)
# fail the module - exit
#validate k8ci ID
validated_k8ci_id = self.k8s_k8ci_find(arg_amodule.params['k8ci_id'])
if not validated_k8ci_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find K8CI ID {}.".format(arg_amodule.params['k8ci_id'])
self.fail_json(**self.result)
self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
arg_amodule.params['k8ci_id'] = validated_k8ci_id
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=arg_amodule.params['id'],
k8s_name=arg_amodule.params['name'],
rg_id=validated_rg_id,
check_state=False)
if self.k8s_id:
self.k8s_should_exist = True
self.acc_id = self.k8s_info['accountId']
# check workers and groups for add or remove?
return
def package_facts(self,check_mode=False):
ret_dict = dict(
name="",
state="CHECK_MODE",
account_id=0,
rg_id=0,
config=None,
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
#if self.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'] = self.k8s_info['id']
ret_dict['name'] = self.k8s_info['name']
ret_dict['techStatus'] = self.k8s_info['techStatus']
ret_dict['state'] = self.k8s_info['status']
ret_dict['rg_id'] = self.rg_id
ret_dict['account_id'] = self.acc_id
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig()
return ret_dict
def nop(self):
"""No operation (NOP) handler for Compute management by decort_kvmvm module.
This function is intended to be called from the main switch construct of the module
when current state -> desired state change logic does not require any changes to
the actual Compute state.
"""
self.result['failed'] = False
self.result['changed'] = False
if self.k8s_id:
self.result['msg'] = ("No state change required for K8s ID {} because of its "
"current status '{}'.").format(self.k8s_id, self.k8s_info['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent K8s instance.").format(self.amodule.params['state'])
return
if arg_k8s_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.k8s_id:
self.result['msg'] = ("Invalid target state '{}' requested for K8s cluster ID {} in the "
"current status '{}'.").format(self.k8s_id,
self.amodule.params['state'],
self.k8s_info['status'])
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent K8s Cluster name '{}' "
"in RG ID {} / name '{}'").format(self.amodule.params['state'],
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['rg_name'])
return
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']
def create(self):
self.k8s_provision(self.amodule.params['name'],
self.amodule.params['k8ci_id'],
self.amodule.params['rg_id'],
self.amodule.params['master_count'],
self.amodule.params['master_cpu'],
self.amodule.params['master_ram_mb'],
self.amodule.params['master_disk_gb'],
self.amodule.params['workers'][0],
self.amodule.params['extnet_id'],
self.amodule.params['with_lb'],
self.amodule.params['description'],)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=self.amodule.params['id'],
k8s_name=self.amodule.params['name'],
rg_id=self.rg_id,
check_state=False)
if self.k8s_id:
self.k8s_should_exist = True
if self.k8s_id and self.amodule.params['workers'][1]:
self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers'])
return
def destroy(self):
self.k8s_delete(self.k8s_id)
self.k8s_info['status'] = 'DELETED'
self.k8s_should_exist = False
return
def action(self,disared_state,started=True):
self.k8s_state(self.k8s_info, disared_state,started)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=self.amodule.params['id'],
k8s_name=self.amodule.params['name'],
rg_id=self.rg_id,
check_state=False)
if started == True and self.k8s_info['techStatus'] == "STOPPED":
self.k8s_state(self.k8s_info, disared_state,started)
self.k8s_info['techStatus'] == "STARTED"
self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers'])
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',
return
@staticmethod
def build_parameters():
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_APP_ID'])),
app_secret=dict(type='str',
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_APP_SECRET']),
fallback=(env_fallback, ['DECORT_PASSWORD']),
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),
)
quotas=dict(type='dict', required=False),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present','check']),
permanent=dict(type='bool', default=False),
started=dict(type='bool', default=True),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
name=dict(type='str', required=True),
id=dict(type='int', required=False, default=0),
getConfig=dict(type='bool',required=False, default=False),
rg_id=dict(type='int', default=0),
rg_name=dict(type='str',default=""),
k8ci_id=dict(type='int', required=True),
wg_name=dict(type='str', required=False),
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=10),
workers=dict(type='list'),
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()
module_parameters = decort_k8s.build_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
@@ -125,112 +263,74 @@ def main():
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['id', 'name'],
['rg_id','rg_name']
],
)
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
subj = decort_k8s(amodule)
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
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.k8s_id:
# cluster is found - package facts and report success to Ansible
subj.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate K8s cluster name '{}'. "
"RG ID {}").format(amodule.params['name'],
amodule.params['rg_id'],)
amodule.fail_json(**subj.result)
if subj.k8s_id:
if subj.k8s_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"):
subj.error()
elif subj.k8s_info['status'] == "DELETED":
if amodule.params['state'] in ('disabled', 'enabled', 'present'):
subj.k8s_restore(subj.k8s_id)
subj.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
subj.nop()
elif subj.k8s_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'disabled':
subj.action(amodule.params['state'])
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'])
subj.destroy()
else:
subj.action(amodule.params['state'],amodule.params['started'])
elif subj.k8s_info['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
subj.destroy()
elif amodule.params['state'] in ('present','enabled'):
subj.action(amodule.params['state'],amodule.params['started'])
else:
subj.nop()
elif subj.k8s_info['status'] == "DESTROED":
if amodule.params['state'] in ('present','enabled'):
subj.create()
if amodule.params['state'] == 'absent':
subj.nop()
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)
subj.nop()
if amodule.params['state'] in ('present','started'):
subj.create()
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
subj.error()
if subj.result['failed']:
amodule.fail_json(**subj.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 subj.k8s_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
else:
amodule.exit_json(**subj.result)
if __name__ == "__main__":
main()

View File

@@ -192,6 +192,9 @@ options:
- If I(ssh_key) is not specified, this parameter is ignored and a warning is generated.
- This parameter is valid at VM creation time only and ignored for any operation on existing VMs.
required: no
user_data:
description:
- Cloud-init User-Data, exept ssh module
state:
description:
- Specify the desired state of the virtual machine at the exit of the module.
@@ -548,15 +551,18 @@ class decort_kvmvm(DecortController):
if self.amodule.params['state'] in ('halted', 'poweredoff'):
start_compute = False
if self.amodule.params['ssh_key'] and self.amodule.params['ssh_key_user']:
if self.amodule.params['ssh_key'] and self.amodule.params['ssh_key_user'] and not self.amodule.params['ci_user_data']:
cloud_init_params = {'users': [
{"name": self.amodule.params['ssh_key_user'],
"ssh-authorized-keys": [self.amodule.params['ssh_key']],
"shell": '/bin/bash'}
]}
elif self.amodule.params['ci_user_data']:
cloud_init_params = {}
for ci_param in self.amodule.params['ci_user_data']:
cloud_init_params.update(ci_param)
else:
cloud_init_params = None
# if we get through here, all parameters required to create new Compute instance should be at hand
# NOTE: KVM VM is created in HALTED state and must be explicitly started
@@ -595,6 +601,11 @@ class decort_kvmvm(DecortController):
# Next manage data disks
self.compute_data_disks(self.comp_info, self.amodule.params['data_disks'])
self.compute_affinity(self.comp_info,
self.amodule.params['tag'],
self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label'],)
# NOTE: see NOTE above regarding libvirt "feature" and new VMs created in HALTED state
if self.amodule.params['state'] not in ('halted', 'poweredoff'):
self.compute_powerstate(self.comp_info, 'started')
@@ -641,6 +652,11 @@ class decort_kvmvm(DecortController):
self.compute_resize(self.comp_info,
self.amodule.params['cpu'], self.amodule.params['ram'],
wait_for_state_change=arg_wait_cycles)
self.compute_affinity(self.comp_info,
self.amodule.params['tag'],
self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label'],)
return
def package_facts(self, check_mode=False):
@@ -774,6 +790,11 @@ class decort_kvmvm(DecortController):
rg_name=dict(type='str', default=""),
ssh_key=dict(type='str', required=False),
ssh_key_user=dict(type='str', required=False),
tag=dict(type='list', required=False),
affinity_label=dict(type='str', required=False),
aff_rule=dict(type='list', required=False),
aaff_rule=dict(type='list', required=False),
ci_user_data=dict(type='list', required=False),
state=dict(type='str',
default='present',
choices=['absent', 'paused', 'poweredoff', 'halted', 'poweredon', 'present', 'check']),

View File

@@ -242,6 +242,7 @@ facts:
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
import paramiko
from ansible.module_utils.decort_utils import *
@@ -316,6 +317,9 @@ def decort_vins_parameters():
ext_net_id=dict(type='int', required=False, default=-1),
ext_ip_addr=dict(type='str', required=False, default=''),
ipcidr=dict(type='str', required=False, default=''),
mgmtaddr=dict(type='str',required=False, default=''),
custom_config=dict(type='bool',required=False, default=False),
config_save=dict(type='bool',required=False, default=False),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
@@ -387,6 +391,7 @@ def main():
vins_level = "ID"
validated_acc_id = vins_facts['accountId']
validated_rg_id = vins_facts['rgId']
elif amodule.params['rg_id']:
# expect ViNS @ RG level in the RG with specified ID
vins_level = "RG"
@@ -443,7 +448,6 @@ def main():
# rg_name without account specified
decon.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0."
decon.fail_json(**decon.result)
#
# Initial validation of module arguments is complete
#
@@ -457,7 +461,7 @@ def main():
#
# "MODELED", "CREATED", "ENABLED", "ENABLING", "DISABLED", "DISABLING", "DELETED", "DELETING", "DESTROYED", "DESTROYING"
#
# if cconfig_save is true, only config save without other updates
vins_should_exist = False
if vins_id:
@@ -491,7 +495,9 @@ def main():
elif amodule.params['state'] in ('present', 'enabled'):
# update ViNS
decon.vins_update(vins_facts,
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'])
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'],
amodule.params['mgmtaddr'],
)
elif amodule.params['state'] == 'disabled':
# disable and update ViNS
decon.vins_state(vins_facts, 'disabled')
@@ -586,6 +592,10 @@ def main():
# be returned.
_, vins_facts = decon.vins_find(vins_id)
decon.result['facts'] = decort_vins_package_facts(vins_facts, amodule.check_mode)
# add password to facts if mgmtaddr is present
# need reworking
if amodule.params['mgmtaddr'] != "":
decon.result['facts'].update({'password': vins_facts['VNFDev']['config']['mgmt']['password']})
amodule.exit_json(**decon.result)

View File

@@ -24,7 +24,7 @@ NOTE: this utility library requires DECORT platform version 3.4.0 or higher.
It is not compatible with older versions.
Requirements:
- python >= 2.6
- python >= 3.8
- PyJWT Python module
- requests Python module
- netaddr Python module
@@ -1188,6 +1188,47 @@ class DecortController(object):
return True
return False
def compute_affinity(self,comp_dict,tags,aff,aaff,label=""):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "compute_affinity")
api_params = dict(computeId=comp_dict['id'])
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRulesClear", api_params)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRulesClear", api_params)
if tags:
for tag in tags:
api_params = dict(computeId=comp_dict['id'],
key=tag['key'],
value=tag['value'], )
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/tagAdd", api_params)
if label:
api_params = dict(computeId=comp_dict['id'],
affinityLabel=label,)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelSet", api_params)
if aff:
if len(aff[0])>0:
for rule in aff:
api_params = dict(computeId=comp_dict['id'],
key=rule['key'],
value=rule['value'],
topology=rule['topology'],
mode=rule['mode'],
policy=rule['policy'],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleAdd", api_params)
if aaff:
if len(aaff[0])>0:
for rule in aaff:
api_params = dict(computeId=comp_dict['id'],
key=rule['key'],
value=rule['value'],
topology=rule['topology'],
mode=rule['mode'],
policy=rule['policy'],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRuleAdd", api_params)
self.result['failed'] = False
self.result['changed'] = True
###################################
# OS image manipulation methods
@@ -2105,10 +2146,10 @@ class DecortController(object):
expected_state = ""
if vins_dict['status'] in ["CREATED", "ENABLED"] and desired_state == 'disabled':
rgstate_api = "/restmachine/cloudapi/vins/disable"
vinsstate_api = "/restmachine/cloudapi/vins/disable"
expected_state = "DISABLED"
elif vins_dict['status'] == "DISABLED" and desired_state == 'enabled':
rgstate_api = "/restmachine/cloudapi/vins/enable"
vinsstate_api = "/restmachine/cloudapi/vins/enable"
expected_state = "ENABLED"
if vinsstate_api != "":
@@ -2125,7 +2166,7 @@ class DecortController(object):
desired_state)
return
def vins_update(self, vins_dict, ext_net_id, ext_ip_addr=""):
def vins_update(self, vins_dict, ext_net_id, ext_ip_addr="", mgmtaddr=""):
"""Update ViNS. Currently only updates to the external network connection settings and
external IP address assignment are implemented.
Note that as ViNS created at account level cannot have external connections, attempt
@@ -2150,6 +2191,11 @@ class DecortController(object):
self.result['msg'] = ("vins_update() in check mode: updating ViNS ID {}, name '{}' "
"was requested.").format(vins_dict['id'], vins_dict['name'])
return
if self.amodule.params['config_save'] and vins_dict['VNFDev']['customPrecfg']:
# only save config,no other modifictaion
self.result['changed'] = True
self._vins_vnf_config_save(vins_dict['VNFDev']['id'])
return
if not vins_dict['rgId']:
# this ViNS exists at account level - no updates are possible
@@ -2201,7 +2247,78 @@ class DecortController(object):
"no reconnection to default network will be done.").format(vins_dict['id'],
gw_config[
'ext_net_id'])
for iface in vins_dict['VNFDev']['interfaces']:
if iface['ipAddress'] == mgmtaddr:
if not iface['listenSsh']:
self._vins_vnf_addmgmtaddr(vins_dict['VNFDev']['id'],mgmtaddr)
elif mgmtaddr =="":
if iface['listenSsh'] and iface['name'] != "ens9":
self._vins_vnf_delmgmtaddr(vins_dict['VNFDev']['id'],iface['ipAddress'])
if self.amodule.params['custom_config']:
if not vins_dict['VNFDev']['customPrecfg']:
self._vins_vnf_config_save(vins_dict['VNFDev']['id'])
self._vins_vnf_customconfig_set(vins_dict['VNFDev']['id'])
else:
if vins_dict['VNFDev']['customPrecfg']:
self._vins_vnf_customconfig_set(vins_dict['VNFDev']['id'],False)
return
def _vins_vnf_addmgmtaddr(self,dev_id,mgmtip):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_addmgmtaddr")
api_params = dict(devId=dev_id,ip=mgmtip)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/addMgmtAddr", api_params)
if api_resp.status_code == 200:
self.result['changed'] = True
self.result['failed'] = False
else:
self.result['warning'] = ("_vins_vnf_addmgmtaddr(): failed to add MGMT addr VNFID {} iface ADDR {}. HTTP code {}, "
"response {}.").format(dev_id,mgmtip,api_resp.status_code, api_resp.reason)
return
def _vins_vnf_delmgmtaddr(self,dev_id,mgmtip):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_delmgmtaddr")
api_params = dict(devId=dev_id,ip=mgmtip)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/delMgmtAddr", api_params)
if api_resp.status_code == 200:
self.result['changed'] = True
self.result['failed'] = False
else:
self.result['warning'] = ("_vins_vnf_delmgmtaddr(): failed to delete MGMT addr VNFID {} iface ADDR {}. HTTP code {}, "
"response {}.").format(dev_id,mgmtip,api_resp.status_code, api_resp.reason)
return
def _vins_vnf_customconfig_set(self,dev_id,arg_mode=True):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_customconfig_set")
api_params = dict(devId=dev_id,mode=arg_mode)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/customSet", api_params)
if api_resp.status_code == 200:
self.result['changed'] = True
self.result['failed'] = False
else:
self.result['warning'] = ("_vins_vnf_customconfig_set(): failed to enable or disable Custom pre-config mode on the VNF device. {}. HTTP code {}, "
"response {}.").format(dev_id,api_resp.status_code, api_resp.reason)
return
def _vins_vnf_config_save(self,dev_id):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_config_save")
api_params = dict(devId=dev_id)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/configSave", api_params)
if api_resp.status_code == 200:
self.result['changed'] = True
self.result['failed'] = False
else:
self.result['warning'] = ("_vins_vnf_config_set(): failed to Save configuration on the VNF device. {}. HTTP code {}, "
"response {}.").format(dev_id,api_resp.status_code, api_resp.reason)
return
def vins_vnf_ifaceadd(self):
return
def vins_vnf_ifaceremove(self):
return
##############################
@@ -2678,7 +2795,12 @@ class DecortController(object):
return ret_k8s_id, ret_k8s_dict
def k8s_find(self, arg_k8s_id=0, arg_k8s_name="", arg_check_state=True):
##############################
#
# K8s management
#
##############################
def k8s_find(self, k8s_id, k8s_name="",rg_id=0,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.
@@ -2700,7 +2822,7 @@ class DecortController(object):
# Transient state (ending with ING) are invalid from k8s manipulation viewpoint
#
K8S_INVALID_STATES = ["MODELED"]
K8S_INVALID_STATES = ["MODELED","DESTROYED","DESTROYING"]
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_find")
@@ -2708,31 +2830,22 @@ class DecortController(object):
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 k8s_id:
ret_k8s_id, ret_k8s_dict = self._k8s_get_by_id(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.result['msg'] = "k8s_find(): cannot find k8s cluster by ID {}.".format(k8s_id)
self.amodule.fail_json(**self.result)
elif arg_k8s_name != "":
else:
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)
k8s_list = json.loads(api_resp.content.decode('utf8'))
for k8s_item in k8s_list:
if k8s_item['name'] == k8s_name and k8s_item['rgId'] == rg_id:
if not check_state or k8s_item['status'] not in K8S_INVALID_STATES:
ret_k8s_id = k8s_item['id']
_, ret_k8s_dict = self._k8s_get_by_id(ret_k8s_id)
return ret_k8s_id, ret_k8s_dict
@@ -2753,7 +2866,7 @@ class DecortController(object):
"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 {} "
@@ -2772,7 +2885,8 @@ class DecortController(object):
"'{}' was requested.").format(arg_k8s_dict['id'], arg_k8s_dict['name'],
arg_desired_state)
return
if arg_desired_state == 'present':
arg_desired_state = 'enabled'
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 = ""
@@ -2802,23 +2916,27 @@ class DecortController(object):
"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)
self.result['msg'] = "k8s_delete() in check mode: delete K8s cluster ID {} was requested.".format(k8s_id)
return
api_params = dict(k8sId=k8s_id,
permanently=permanently,
permanently=False,
)
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['msg'] = "k8s_delete() K8s cluster ID {} was deleted.".format(k8s_id)
self.result['changed'] = True
return
def k8s_restore(self, k8s_id ):
"""Restores a deleted k8s cluster identified by ID.
@@ -2838,13 +2956,20 @@ class DecortController(object):
self.result['failed'] = False
self.result['changed'] = True
return
def k8s_enable(self,k8s_id):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_enable")
api_params = dict(k8sId=k8s_id)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/enable", api_params)
self.result['failed'] = False
self.result['changed'] = True
return
def k8s_provision(self, k8s_name,
wg_name, k8ci_id,
rg_id, master_count,
k8ci_id,rg_id, master_count,
master_cpu, master_ram,
master_disk, worker_count,
worker_cpu, worker_ram,
worker_disk, extnet_id,
master_disk, default_worker, extnet_id,
with_lb, annotation, ):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_provision")
@@ -2854,20 +2979,31 @@ class DecortController(object):
self.result['msg'] = ("k8s_provision() in check mode. Provision k8s '{}' in RG ID {} "
"was requested.").format(k8s_name, rg_id)
return 0
def_wg_name = default_worker['name']
def_wg_count = default_worker['num']
def_wg_cpu = default_worker['cpu']
def_wg_ram = default_worker['ram']
def_wg_disk = default_worker['disk']
def_wg_lab = default_worker['labels'] if "labels" in default_worker else None
def_wg_taints = default_worker['taints'] if "taints" in default_worker else None
def_wg_ann = default_worker['annotations'] if "annotations" in default_worker else None
api_url = "/restmachine/cloudapi/k8s/create"
api_params = dict(name=k8s_name,
rgId=rg_id,
k8ciId=k8ci_id,
workerGroupName=wg_name,
workerGroupName=def_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,
workerNum=def_wg_count,
workerCpu=def_wg_cpu,
workerRam=def_wg_ram,
workerDisk=def_wg_disk,
labels=def_wg_lab,
taints=def_wg_taints,
annotations=def_wg_ann,
extnetId=extnet_id,
withLB=with_lb,
desc=annotation,
@@ -2902,3 +3038,451 @@ class DecortController(object):
else:
self.result['failed'] = True
return
def k8s_workers_modify(self,arg_k8swg,arg_modwg):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_workers_modify")
if self.k8s_info['techStatus'] != "STARTED":
self.result['changed'] = False
self.result['msg'] = ("k8s_workers_modify(): Can't modify with TechStatus other then STARTED")
return
wg_del_list = []
wg_add_list = []
wg_modadd_list = []
wg_moddel_list = []
wg_outer = [rec['name'] for rec in arg_modwg]
wg_inner = [rec['name'] for rec in arg_k8swg['k8sGroups']['workers']]
for rec in arg_k8swg['k8sGroups']['workers']:
if rec['name'] not in wg_outer:
wg_del_list.append(rec['id'])
for rec in arg_modwg:
if rec['name'] not in wg_inner:
wg_add_list.append(rec)
for rec_inn in arg_k8swg['k8sGroups']['workers']:
for rec_out in arg_modwg:
if rec_inn['num'] != rec_out['num'] and rec_out['num'] != 0:
count = rec_inn['num']-rec_out['num']
cmp_list = []
if count > 0:
for cmp in rec_inn['detailedInfo'][:count]:
cmp_list.append(cmp['id'])
wg_moddel_list.append({rec_inn['id']:cmp_list})
if count < 0:
wg_modadd_list.append({rec_inn['id']:abs(count)})
if wg_del_list:
for wgid in wg_del_list:
api_params = dict(k8sId=self.k8s_id,workersGroupId=wgid)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/workersGroupDelete", api_params)
self.result['changed'] = True
if wg_add_list:
for wg in wg_add_list:
api_params = dict(k8sId=self.k8s_id,
name=wg['name'],
workerNum=wg['num'],
workerCpu=wg['cpu'],
workerRam=wg['ram'],
workerDisk=wg['disk'],
labels=wg['labels'] if "labels" in wg else None,
taints=wg['taints'] if "taints" in wg else None,
annotations=wg['annotations'] if "annotations" in wg else None,
)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/workersGroupAdd", api_params)
self.result['changed'] = True
if wg_modadd_list:
for wg in wg_modadd_list:
for key in wg:
api_params = dict(k8sId=self.k8s_id,workersGroupId=key,num=wg[key])
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/workerAdd", api_params)
self.result['changed'] = True
if wg_moddel_list:
for wg in wg_moddel_list:
for key in wg:
for cmpid in wg[key]:
api_params = dict(k8sId=self.k8s_id,workersGroupId=key,workerId=cmpid)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/deleteWorkerFromGroup", api_params)
self.result['changed'] = True
self.result['failed'] = False
return
def k8s_k8ci_find(self,arg_k8ci_id):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_k8ci_find")
api_params = dict(includeDisabled=False)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8ci/list", api_params)
if api_resp.status_code == 200:
ret_k8ci_list = json.loads(api_resp.content.decode('utf8'))
for k8ci_item in ret_k8ci_list:
if k8ci_item['id'] == arg_k8ci_id:
break
else:
self.result['failed'] = True
self.result['msg'] = "k8s_k8ci_find(): cannot find ID."
self.amodule.fail_json(**self.result)
else:
self.result['failed'] = True
self.result['msg'] = ("Failed to get k8ci list HTTP code {}.").format(api_resp.status_code)
self.amodule.fail_json(**self.result)
return arg_k8ci_id
def k8s_getConfig(self):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_getConfig")
api_params = dict(k8sId=self.k8s_id)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/getConfig", api_params)
ret_conf = api_resp.content.decode('utf8')
return ret_conf
##############################
#
# Bservice management
#
##############################
def bservice_get_by_id(self,bs_id):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_get_by_id")
ret_bs_id = 0
ret_bs_dict = dict()
if not bs_id:
self.result['failed'] = True
self.result['msg'] = "bservice_get_by_id(): zero B-Service ID specified."
self.amodule.fail_json(**self.result)
api_params = dict(serviceId=bs_id, )
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/get", api_params)
if api_resp.status_code == 200:
ret_bs_id = bs_id
ret_bs_dict = json.loads(api_resp.content.decode('utf8'))
else:
self.result['warning'] = ("bservice_get_by_id(): failed to get B-service by ID {}. HTTP code {}, "
"response {}.").format(bs_id, api_resp.status_code, api_resp.reason)
return ret_bs_id, ret_bs_dict
def _bservice_rg_list(self,acc_id,rg_id):
ret_bs_dict=dict()
api_params = dict(accountId=acc_id,rgId=rg_id )
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/list", api_params)
if api_resp.status_code == 200:
ret_bs_dict = json.loads(api_resp.content.decode('utf8'))
else:
self.result['warning'] = ("bservice_rg_list(): failed to get B-service list. HTTP code {}, "
"response {}.").format(api_resp.status_code, api_resp.reason)
return ret_bs_dict
def bservice_find(self,account_id,rg_id,bservice_name="",bservice_id = 0,check_state=True):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_find")
if bservice_id == 0:
bs_rg_list = self._bservice_rg_list(account_id,rg_id)
for srv in bs_rg_list:
if bservice_name == srv['name']:
bservice_id = int(srv['id'])
if bservice_id > 0:
ret_bs_id,ret_bs_dict = self.bservice_get_by_id(bservice_id)
return ret_bs_id,ret_bs_dict
else:
return bservice_id,None
def bservice_provision(self,bs_name,rgid,sshuser=None,sshkey=None):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_provision")
api_url = "/restmachine/cloudapi/bservice/create"
api_params = dict(
name = bs_name,
rgId = rgid,
sshUser = sshuser,
sshKey = sshkey,
)
api_resp = self.decort_api_call(requests.post, api_url, api_params)
self.result['failed'] = False
self.result['changed'] = True
ret_bservice_id = int(api_resp.content.decode('utf8'))
return ret_bservice_id
def bservice_state(self,bs_dict,desired_state,started=True):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_state")
if desired_state == "present":
desired_state = "enabled"
NOP_STATES_FOR_BS_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING",
"DESTROYED","RESTORYNG","RECONFIGURING"]
VALID_TARGET_STATES = ["enabled", "disabled"]
if bs_dict['status'] in NOP_STATES_FOR_BS_CHANGE:
self.result['failed'] = False
self.result['msg'] = ("bservice_state(): no state change possible for ViNS ID {} "
"in its current state '{}'.").format(bs_dict['id'], bs_dict['status'])
return
if desired_state not in VALID_TARGET_STATES:
self.result['failed'] = False
self.result['warning'] = ("bservice_state(): unrecognized desired state '{}' requested "
"for B-service ID {}. No B-service state change will be done.").format(desired_state,
bs_dict['id'])
return
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("bservice_state() in check mode: setting state of B-service ID {}, name '{}' to "
"'{}' was requested.").format(bs_dict['id'], bs_dict['name'],
desired_state)
return
bsstate_api = "" # this string will also be used as a flag to indicate that API call is necessary
api_params = dict(serviceId=bs_dict['id'])
expected_state = ""
if bs_dict['status'] in ["CREATED", "ENABLED"] and desired_state == 'disabled':
bsstate_api = "/restmachine/cloudapi/bservice/disable"
expected_state = "DISABLED"
elif bs_dict['status'] in ["CREATED", "DISABLED"] and desired_state == 'enabled':
bsstate_api = "/restmachine/cloudapi/bservice/enable"
expected_state = "ENABLED"
if bsstate_api != "":
self.decort_api_call(requests.post, bsstate_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
bs_dict['status'] = expected_state
else:
self.result['failed'] = False
self.result['msg'] = ("bservice_state(): no state change required for B-service ID {} from current "
"state '{}' to desired state '{}'.").format(bs_dict['id'],
bs_dict['status'],
desired_state)
start_api = ""
if bs_dict['status'] in ["ENABLED","enabled"]:
if started == True and bs_dict['techStatus'] == "STOPPED":
start_api = "/restmachine/cloudapi/bservice/start"
t_state_expected = "STARTED"
if started == False and bs_dict['techStatus'] == "STARTED":
start_api = "/restmachine/cloudapi/bservice/stop"
t_state_expected = "STOPPED"
if start_api != "":
self.decort_api_call(requests.post, start_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
bs_dict['techStatus'] = t_state_expected
else:
self.result['failed'] = False
self.result['msg'] = ("bservice_state(): no start/stop action required for B-service ID {} from current "
"techStatus '{}' to desired started = '{}'.").format(bs_dict['id'],
bs_dict['techStatus'],
started)
return
def bservice_delete(self,bs_id,permanently=True):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_delete")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = "bservice_delete() in check mode: delete B-Service ID {} was requested.".format(bs_id)
return
api_params = dict(serviceId=bs_id,permanently=permanently)
self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/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['msg'] = "bservice_delete() B-Service ID {} was deleted.".format(bs_id)
self.result['changed'] = True
return
#
# GROUP MANAGE
#
def _group_get_by_id(self,bs_id,g_id):
api_params = dict(serviceId=bs_id,compgroupId=g_id)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/groupGet", api_params)
if api_resp.status_code == 200:
ret_gr_id = g_id
ret_gr_dict = json.loads(api_resp.content.decode('utf8'))
else:
self.result['warning'] = ("group_get_by_id(): failed to get Group by ID {}. HTTP code {}, "
"response {}.").format(g_id, api_resp.status_code, api_resp.reason)
return ret_gr_id,ret_gr_dict
def group_find(self,bs_id,bs_info,group_id=0,group_name=""):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_find")
if group_id == 0:
try:
i = bs_info['groupsName'].index(group_name)
except:
return 0,None
group_id = int(bs_info['groups'][i])
return self._group_get_by_id(bs_id,group_id)
def group_state(self,bs_id,gr_id,desired_state):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_state")
group_api=""
if desired_state == 'stopped':
group_api = "/restmachine/cloudapi/bservice/groupStop"
state_expected = "STOPPED"
else:
group_api = "/restmachine/cloudapi/bservice/groupStart"
state_expected = "STARTED"
api_params = dict(
serviceId=bs_id,
compgroupId=gr_id
)
if group_api != "":
self.decort_api_call(requests.post, group_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
else:
self.result['failed'] = False
self.result['msg'] = ("group_state(): no start/stop action required for B-service ID {} "
"to desired state '{}'.").format(bs_id,desired_state)
return
def group_resize_count(self,bs_id,gr_dict,desired_count):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_resize_count")
count = len(gr_dict['computes'])
if desired_count != count:
api_params=dict(
serviceId=bs_id,
compgroupId=gr_dict['id'],
count=desired_count,
mode="ABSOLUTE"
)
api_url = "/restmachine/cloudapi/bservice/groupResize"
self.decort_api_call(requests.post, api_url, api_params)
self.result['failed'] = False
self.result['changed'] = True
self.need_update_group_info = True
else:
self.result['failed'] = False
self.result['msg'] = ("group_resize_count(): no need resize Group ID {}.").format(gr_dict['id'])
return
def group_update_hw(self,bs_id,gr_dict,arg_cpu,arg_disk,arg_name,arg_role,arg_ram):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_update_hw")
api_params=dict(
serviceId=bs_id,
compgroupId=gr_dict['id'],
force=True,
)
if gr_dict['cpu'] != arg_cpu:
api_params.update({'cpu': arg_cpu})
if gr_dict['ram'] != arg_ram:
api_params.update({'ram': arg_ram})
if gr_dict['role'] != arg_role:
api_params.update({'role': arg_role})
if gr_dict['disk'] != arg_disk:
api_params.update({'disk': arg_disk})
if gr_dict['name'] != arg_name:
api_params.update({'name': arg_name})
api_url = "/restmachine/cloudapi/bservice/groupUpdate"
if len(api_params) > 3:
#
self.decort_api_call(requests.post, api_url, api_params)
self.result['failed'] = False
self.result['changed'] = True
self.need_update_group_info = True
else:
self.result['failed'] = False
self.result['msg'] = ("group_update_hw(): no need update Group ID {}.").format(gr_dict['id'])
return
def group_update_net(self,bs_id,gr_dict,arg_net):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_update_net")
list_vins= list()
list_extnet= list()
for net in arg_net:
if net['type'] == 'VINS':
list_vins.append(net['id'])
else:
list_extnet.append(net['id'])
if gr_dict['vinses'] != list_vins:
api_url = "/restmachine/cloudapi/bservice/groupUpdateVins"
api_params = dict(
serviceId=bs_id,
compgroupId=gr_dict['id'],
vinses=list_vins
)
self.decort_api_call(requests.post, api_url, api_params)
self.result['failed'] = False
self.result['changed'] = True
#extnet connection need stoped status of group
#rly need connect group to extnet ?
return
def group_provision(
self,bs_id,arg_name,arg_count=1,arg_cpu=1,arg_ram=1024,
arg_boot_disk=10,arg_image_id=0,arg_driver="KVM_X86",arg_role="",
arg_network=None,arg_timeout=0
):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_provision")
list_vins= list()
for net in arg_network:
if net['type'] == 'VINS':
list_vins.append(net['id'])
api_url = "/restmachine/cloudapi/bservice/groupAdd"
api_params = dict(
serviceId = bs_id,
name = arg_name,
count = arg_count,
cpu = arg_cpu,
ram = arg_ram,
disk = arg_boot_disk,
imageId = arg_image_id,
driver = arg_driver,
role = arg_role,
vinses = list_vins,
timeoutStart = arg_timeout
)
self.decort_api_call(requests.post, api_url, api_params)
self.result['failed'] = False
self.result['changed'] = True
return
def group_delete(self,bs_id,gr_id):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_delete")
api_url = "/restmachine/cloudapi/bservice/groupRemove"
api_params=dict(
serviceId = bs_id,
compgroupId = gr_id
)
self.decort_api_call(requests.post, api_url, api_params)
self.result['failed'] = False
self.result['msg'] = "group_delete() Group ID {} was deleted.".format(gr_id)
self.result['changed'] = True
return