Automatic Deployment 51/151/7
authorSzilard Cserey <szilard.cserey@ericsson.com>
Wed, 18 Mar 2015 14:49:24 +0000 (15:49 +0100)
committerSzilard Cserey <szilard.cserey@ericsson.com>
Thu, 2 Apr 2015 10:15:28 +0000 (12:15 +0200)
- Deployment Hardware Adapter
- Deployment Environment Adapter
- Fuel environment cleanup
- Fuel environment configuration

JIRA: [BGS-2] Create Fuel deployment script

Change-Id: Ie8aa6c0817a73c507cb4345bde1e2c904bb5b105
Signed-off-by: Szilard Cserey <szilard.cserey@ericsson.com>
fuel/ci/deploy.sh [changed mode: 0644->0755]
fuel/deploy/dea.py [new file with mode: 0644]
fuel/deploy/dea.yaml [new file with mode: 0644]
fuel/deploy/deploy.py [new file with mode: 0644]
fuel/deploy/dha.py [new file with mode: 0644]

old mode 100644 (file)
new mode 100755 (executable)
index e69de29..d11c65a
@@ -0,0 +1,3 @@
+# To be able to deploy on a certain metal environment there needs to be a Deployment Environment Adaptor executable"
+# properly added to $PATH such that deploy.sh can call it by $dea [options] as indicated by ./deploy -h.
+
diff --git a/fuel/deploy/dea.py b/fuel/deploy/dea.py
new file mode 100644 (file)
index 0000000..0ab215d
--- /dev/null
@@ -0,0 +1,52 @@
+import yaml
+
+class DeploymentEnvironmentAdapter(object):
+    def __init__(self):
+        self.dea_struct = None
+        self.blade_ids = {}
+        self.blades = {}
+        self.shelf_ids = []
+
+    def parse_yaml(self, yaml_path):
+        with open(yaml_path) as yaml_file:
+            self.dea_struct = yaml.load(yaml_file)
+        self.collect_shelf_and_blade_info()
+
+    def get_no_of_blades(self):
+        no_of_blades = 0
+        for shelf in self.dea_struct['shelf']:
+            no_of_blades += len(shelf['blade'])
+        return no_of_blades
+
+    def get_server_type(self):
+        return self.dea_struct['server_type']
+
+    def get_environment_name(self):
+        return self.dea_struct['name']
+
+    def get_shelf_ids(self):
+        return self.shelf_ids
+
+    def get_blade_ids(self, shelf_id):
+        return self.blade_ids[shelf_id]
+
+    def collect_shelf_and_blade_info(self):
+        self.blade_ids = {}
+        self.blades = {}
+        self.shelf_ids = []
+        for shelf in self.dea_struct['shelf']:
+             self.shelf_ids.append(shelf['id'])
+             blade_ids = self.blade_ids[shelf['id']] = []
+             blades = self.blades[shelf['id']] = {}
+             for blade in shelf['blade']:
+                 blade_ids.append(blade['id'])
+                 blades[blade['id']] = blade
+
+    def is_controller(self, shelf_id, blade_id):
+        blade = self.blades[shelf_id][blade_id]
+        return (True if 'role' in blade and blade['role'] == 'controller'
+                else False)
+
+    def is_compute_host(self, shelf_id, blade_id):
+        blade = self.blades[shelf_id][blade_id]
+        return True if 'role' not in blade else False
\ No newline at end of file
diff --git a/fuel/deploy/dea.yaml b/fuel/deploy/dea.yaml
new file mode 100644 (file)
index 0000000..5ade83f
--- /dev/null
@@ -0,0 +1,26 @@
+---
+name: ENV-1
+server_type: esxi
+shelf:
+ - id: 1
+   blade:
+    - id: 1
+      role: controller
+    - id: 2
+    - id: 3
+      role: controller
+    - id: 4
+    - id: 5
+    - id: 6
+networking:
+ switch_type: esxi
+ switch_mgmt_ip: 192.168.0.1/24
+ vlan:
+  - name: traffic
+    tag: 100
+  - name: storage
+    tag: 102
+  - name: control
+    tag: 101
+  - name: management
+...
\ No newline at end of file
diff --git a/fuel/deploy/deploy.py b/fuel/deploy/deploy.py
new file mode 100644 (file)
index 0000000..4df4f36
--- /dev/null
@@ -0,0 +1,202 @@
+import subprocess
+import sys
+import time
+import os
+from dha import DeploymentHardwareAdapter
+from dea import DeploymentEnvironmentAdapter
+
+SUPPORTED_RELEASE = 'Juno on CentOS 6.5'
+N = {'id': 0, 'status': 1, 'name': 2, 'cluster': 3, 'ip': 4, 'mac': 5,
+     'roles': 6, 'pending_roles': 7, 'online': 8}
+E = {'id': 0, 'status': 1, 'name': 2, 'mode': 3, 'release_id': 4,
+     'changes': 5, 'pending_release_id': 6}
+R = {'id': 0, 'name': 1, 'state': 2, 'operating_system': 3, 'version': 4}
+RO = {'name': 0, 'conflicts': 1}
+
+def exec_cmd(cmd):
+    process = subprocess.Popen(cmd,
+                               stdout=subprocess.PIPE,
+                               stderr=subprocess.STDOUT,
+                               shell=True)
+    return process.communicate()[0]
+
+def parse(printout):
+   parsed_list = []
+   lines = printout.splitlines()
+   for l in lines[2:]:
+        parsed = [e.strip() for e in l.split('|')]
+        parsed_list.append(parsed)
+   return parsed_list
+
+def err(error_message):
+    sys.stderr.write(error_message)
+    sys.exit(1)
+
+
+
+class Deploy(object):
+
+    def __init__(self):
+        self.supported_release = None
+
+    def get_id_list(self, list):
+        return [l[0] for l in list]
+
+    def cleanup_fuel_environments(self, env_list):
+        WAIT_LOOP = 10
+        SLEEP_TIME = 2
+        id_list = self.get_id_list(env_list)
+        for id in id_list:
+            exec_cmd('fuel env --env %s --delete' % id)
+            for i in range(WAIT_LOOP):
+                if id in self.get_id_list(parse(exec_cmd('fuel env list'))):
+                    time.sleep(SLEEP_TIME)
+                else:
+                    continue
+
+    def cleanup_fuel_nodes(self, node_list):
+        for node in node_list:
+            if node[N['status']] == 'discover':
+                exec_cmd('fuel node --node-id %s --delete-from-db'
+                         % node[N['id']])
+                exec_cmd('dockerctl shell cobbler cobbler system remove '
+                         '--name node-%s' % node[N['id']])
+
+    def check_previous_installation(self):
+        env_list = parse(exec_cmd('fuel env list'))
+        if env_list:
+            self.cleanup_fuel_environments(env_list)
+        node_list = parse(exec_cmd('fuel node list'))
+        if node_list:
+            self.cleanup_fuel_nodes(node_list)
+
+    def check_supported_release(self):
+        release_list= parse(exec_cmd('fuel release -l'))
+        for release in release_list:
+            if release[R['name']] == SUPPORTED_RELEASE:
+                self.supported_release = release
+                break
+        if not self.supported_release:
+            err("This Fuel doesn't contain the following "
+                "release: %s\n" % SUPPORTED_RELEASE)
+
+    def check_role_definitions(self):
+        role_list= parse(exec_cmd('fuel role --release %s'
+                                  % self.supported_release[R['id']]))
+        roles = [role[RO['name']] for role in role_list]
+        if 'compute' not in roles:
+            err("Role compute does not exist in release %"
+                % self.supported_release[R['name']])
+        if 'controller' not in roles:
+            err("Role controller does not exist in release %"
+                % self.supported_release[R['name']])
+
+    def check_prerequisites(self):
+        self.check_supported_release()
+        self.check_role_definitions()
+        self.check_previous_installation()
+
+    def count_discovered_nodes(self, node_list):
+        discovered_nodes = 0
+        for node in node_list:
+            if node[N['status']] == 'discover':
+                discovered_nodes += 1
+        return discovered_nodes
+
+    def wait_for_discovered_blades(self, no_of_blades):
+        WAIT_LOOP = 10
+        SLEEP_TIME = 2
+        all_discovered = False
+        node_list = parse(exec_cmd('fuel node list'))
+        for i in range(WAIT_LOOP):
+            if (self.count_discovered_nodes(node_list) < no_of_blades):
+                time.sleep(SLEEP_TIME)
+                node_list = parse(exec_cmd('fuel node list'))
+            else:
+                all_discovered = True
+                break
+        if not all_discovered:
+            err("There are %s blades defined, but not all of "
+                "them have been discovered\n" % no_of_blades)
+
+    def assign_cluster_node_ids(self, dha, dea, controllers, compute_hosts):
+        node_list= parse(exec_cmd('fuel node list'))
+        for shelf_id in dea.get_shelf_ids():
+            for blade_id in dea.get_blade_ids(shelf_id):
+                blade_mac_list = dha.get_blade_mac_addresses(
+                    shelf_id, blade_id)
+
+                found = False
+                for node in node_list:
+                    if (node[N['mac']] in blade_mac_list and
+                        node[N['status']] == 'discover'):
+                        found = True
+                        break
+                if found:
+                    if dea.is_controller(shelf_id, blade_id):
+                        controllers.append(node[N['id']])
+                    if dea.is_compute_host(shelf_id, blade_id):
+                        compute_hosts.append(node[N['id']])
+                else:
+                    err("Could not find the Node ID for blade "
+                        "with MACs %s or blade is not in "
+                        "discover status\n" % blade_mac_list)
+
+    def env_exists(self, env_name):
+        env_list = parse(exec_cmd('fuel env --list'))
+        for env in env_list:
+            if env[E['name']] == env_name and env[E['status']] == 'new':
+                return True
+        return False
+
+    def configure_environment(self, dea):
+        env_name = dea.get_environment_name()
+        exec_cmd('fuel env -c --name %s --release %s --mode ha --net neutron '
+                 '--nst vlan' % (env_name, self.supported_release[R['id']]))
+
+        if not self.env_exists(env_name):
+            err("Failed to create environment %s" % env_name)
+
+
+
+def main():
+
+    yaml_path = exec_cmd('pwd').strip() + '/dea.yaml'
+    deploy = Deploy()
+
+    dea = DeploymentEnvironmentAdapter()
+
+    if not os.path.isfile(yaml_path):
+        sys.stderr.write("ERROR: File %s not found\n" % yaml_path)
+        sys.exit(1)
+
+    dea.parse_yaml(yaml_path)
+
+    dha = DeploymentHardwareAdapter(dea.get_server_type())
+
+    deploy.check_prerequisites()
+
+    dha.power_off_blades()
+
+    dha.configure_networking()
+
+    dha.reset_to_factory_defaults()
+
+    dha.set_boot_order()
+
+    dha.power_on_blades()
+
+    dha.get_blade_mac_addresses()
+
+    deploy.wait_for_discovered_blades(dea.get_no_of_blades())
+
+    controllers = []
+    compute_hosts = []
+    deploy.assign_cluster_node_ids(dha, dea, controllers, compute_hosts)
+
+    deploy.configure_environment(dea)
+
+
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file
diff --git a/fuel/deploy/dha.py b/fuel/deploy/dha.py
new file mode 100644 (file)
index 0000000..f78686b
--- /dev/null
@@ -0,0 +1,49 @@
+
+class DeploymentHardwareAdapter(object):
+    def __new__(cls, server_type):
+        if cls is DeploymentHardwareAdapter:
+            if server_type == 'esxi':  return EsxiAdapter()
+            if server_type == 'hp': return HpAdapter()
+            if server_type == 'dell': return DellAdapter()
+            if server_type == 'libvirt': return LibvirtAdapter()
+        return super(DeploymentHardwareAdapter, cls).__new__(cls)
+
+
+class HardwareAdapter(object):
+
+    def power_off_blades(self):
+        raise NotImplementedError
+
+    def power_on_blades(self):
+        raise NotImplementedError
+
+    def power_cycle_blade(self):
+        raise NotImplementedError
+
+    def set_boot_order(self):
+        raise NotImplementedError
+
+    def reset_to_factory_defaults(self):
+        raise NotImplementedError
+
+    def configure_networking(self):
+        raise NotImplementedError
+
+    def get_blade_mac_addresses(self, shelf_id, blade_id):
+        raise NotImplementedError
+
+    def get_blade_hardware_info(self, shelf_id, blade_id):
+        raise NotImplementedError
+
+
+class EsxiAdapter(HardwareAdapter):
+    pass
+
+class LibvirtAdapter(HardwareAdapter):
+    pass
+
+class HpAdapter(HardwareAdapter):
+    pass
+
+class DellAdapter(HardwareAdapter):
+    pass