Added Ampere_Openedge hardware type
[rec.git] / workflows / REC_create.py
1 #!/usr/bin/python3
2 #
3 #       Copyright (c) 2019 AT&T Intellectual Property. All Rights Reserved.
4 #
5 #       Licensed under the Apache License, Version 2.0 (the "License");
6 #       you may not use this file except in compliance with the License.
7 #       You may obtain a copy of the License at
8 #
9 #           http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #       Unless required by applicable law or agreed to in writing, software
12 #       distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 #       WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 #       License for the specific language governing permissions and limitations
15 #       under the License.
16 #
17
18 """
19 REC_create.py - This workflow is used to create a REC POD by way of the remote-installer
20 container.   The remote-installer is started if it is not running.  Parameters passed to
21 this script (via the INPUT.yaml file) are:
22   iso_primary - the main installer.iso file to use
23   iso_secondary - the secondary bootcd.iso file
24   input_yaml - the YAML file passed to remote_installer
25   rc_host - the IP address or DNS name of the RC
26 """
27
28 import datetime
29 import docker
30 import requests, urllib3
31 import os, sys, time, yaml
32 import POD
33
34 WORKDIR      = os.path.abspath(os.path.dirname(__file__))
35 RI_NAME      = 'remote-installer'
36 RI_IMAGE     = 'nexus3.akraino.org:10003/akraino/remote-installer:latest'
37 RI_DIR       = '/workflow/remote-installer'
38 CERT_DIR     = RI_DIR + '/certificates'
39 EXTERNALROOT = '/data'
40 NETWORK      = 'host'
41 WAIT_TIME    = 150
42 HTTPS_PORT   = 8443
43 API_PORT     = 15101
44 ADMIN_PASSWD = 'recAdm1n'
45 REMOVE_ISO   = False
46 HOST_IP      = '127.0.0.1'
47
48 def start(ds, **kwargs):
49     # Read the user input from the POST
50     global HOST_IP
51     urllib3.disable_warnings()
52     yaml = read_yaml(WORKDIR + '/INPUT.yaml')
53     REC_ISO_IMAGE_NAME        = yaml['iso_primary']
54     REC_PROVISIONING_ISO_NAME = yaml['iso_secondary']
55     INPUT_YAML_URL            = yaml['input_yaml']
56     HOST_IP                   = yaml['rc_host']
57     CLOUDNAME                 = 'CL-'+POD.POD
58     ISO                       = '%s/images/install-%s.iso' % (RI_DIR, POD.POD)
59     BOOTISO                   = '%s/images/bootcd-%s.iso'  % (RI_DIR, POD.POD)
60     USERCONF                  = '%s/user-configs/%s/user_config.yaml' % (RI_DIR, CLOUDNAME)
61
62     print('-----------------------------------------------------------------------------------------------')
63     print('                      POD is '+POD.POD)
64     print('                CLOUDNAME is '+CLOUDNAME)
65     print('                  WORKDIR is '+WORKDIR)
66     print('                  HOST_IP is '+HOST_IP)
67     print('             EXTERNALROOT is '+EXTERNALROOT)
68     print('       REC_ISO_IMAGE_NAME is '+REC_ISO_IMAGE_NAME)
69     print('REC_PROVISIONING_ISO_NAME is '+REC_PROVISIONING_ISO_NAME)
70     print('           INPUT_YAML_URL is '+INPUT_YAML_URL)
71     print('                      ISO is '+ISO)
72     print('                  BOOTISO is '+BOOTISO)
73     print('                 USERCONF is '+USERCONF)
74     print('-----------------------------------------------------------------------------------------------')
75
76     # Setup RI_DIR
77     initialize_RI(CLOUDNAME)
78
79     # Fetch the three files into WORKDIR
80     fetchURL(REC_ISO_IMAGE_NAME,        WORKDIR + '/install.iso');
81     fetchURL(REC_PROVISIONING_ISO_NAME, WORKDIR + '/bootcd.iso');
82     fetchURL(INPUT_YAML_URL,            WORKDIR + '/user_config.yaml');
83
84     # Link files to RI_DIR with unique names
85     os.link(WORKDIR + '/install.iso', ISO)
86     os.link(WORKDIR + '/bootcd.iso', BOOTISO)
87     os.link(WORKDIR + '/user_config.yaml', USERCONF)
88     PWFILE = '%s/user-configs/%s/admin_passwd' % (RI_DIR, CLOUDNAME)
89     with open(PWFILE, "w") as f:
90         f.write(ADMIN_PASSWD + '\n')
91
92     # Start the remote_installer
93     client = docker.from_env()
94     namefilt = { 'name': RI_NAME }
95     ri = client.containers.list(filters=namefilt)
96     if len(ri) == 0:
97         print(RI_NAME + ' is not running.')
98         c = start_RI(client)
99
100     else:
101         print(RI_NAME + ' is running.')
102         c = ri[0]
103
104     # Send request to remote_installer
105     id = send_request(HOST_IP, CLOUDNAME, ISO, BOOTISO)
106
107     # Wait up to WAIT_TIME minutes for completion
108     if wait_for_completion(HOST_IP, id, WAIT_TIME):
109         print('Installation failed after %d minutes.' % (WAIT_TIME))
110         sys.exit(1)
111
112     # Remove the ISOs?
113     if REMOVE_ISO:
114         for iso in (WORKDIR + '/install.iso', ISO, WORKDIR + '/bootcd.iso', BOOTISO):
115             os.unlink(iso)
116
117     # Done!
118     print('Installation complete!')
119     # sys.exit(0)  Don't exit as this will cause the task to fail!
120     return 'Complete.'
121
122 def read_yaml(input_file):
123     print('Reading '+input_file+' ...')
124     with open(input_file, 'r') as stream:
125         try:
126             return yaml.safe_load(stream)
127         except yaml.YAMLError as exc:
128             print(exc)
129             sys.exit(1)
130
131 def send_request(ri_ip, CLOUDNAME, ISO, BOOTISO):
132     URL     = 'https://%s:%d/v1/installations' % (ri_ip, API_PORT)
133     print('Sending request to '+URL+' ...')
134     headers = {'Content-type': 'application/json'}
135     content = {
136         'cloud-name': CLOUDNAME,
137         'iso': os.path.basename(ISO),
138         'provisioning-iso': os.path.basename(BOOTISO)
139     }
140     certs    = (CERT_DIR+'/clientcert.pem', CERT_DIR+'/clientkey.pem')
141     response = requests.post(URL, json=content, headers=headers, cert=certs, verify=False)
142     print(response)
143     return response.json().get('uuid')
144
145 def create_podevent(msg='Default msg', level='INFO'):
146     API_HOST = 'http://arc-api:8080'
147     if os.environ.get('LOGGING_USER') and os.environ.get('LOGGING_PASSWORD'):
148         payload  = {'name': os.environ['LOGGING_USER'], 'password': os.environ['LOGGING_PASSWORD']}
149         response = requests.post(API_HOST+'/api/v1/login', json=payload)
150         token    = response.headers['X-ARC-Token']
151         headers  = {'X-ARC-Token': token}
152         payload  = {'uuid': POD.POD, 'level': level, 'message': msg}
153         response = requests.post(API_HOST+'/api/v1/podevent', headers=headers, json=payload)
154
155 def wait_for_completion(ri_ip, id, ntimes):
156     """
157     Wait (up to ntimes minutes) for the remote_installer to finish.
158     Any status other than 'completed' is considered a failure.
159     """
160     status = 'ongoing'
161     URL    = 'https://%s:%d/v1/installations/%s/state' % (ri_ip, API_PORT, id)
162     certs  = (CERT_DIR+'/clientcert.pem', CERT_DIR+'/clientkey.pem')
163     lastevent = ''
164     while status == 'ongoing' and ntimes > 0:
165         time.sleep(60)
166         response = requests.get(URL, cert=certs, verify=False)
167         j = response.json()
168         t = (
169             str(j.get('status')),
170             str(j.get('percentage')),
171             str(j.get('description'))
172         )
173         event = 'Status is %s (%s) %s' % t
174         print('%s: %s' % (datetime.datetime.now().strftime('%x %X'), event))
175         if event != lastevent:
176             create_podevent(event)
177         lastevent = event
178         status = j.get('status')
179         ntimes = ntimes - 1
180     return status != 'completed'
181
182 def fetchURL(url, dest):
183     print('Fetching '+url+' ...')
184     r = requests.get(url)
185     with open(dest, 'wb') as f1:
186         f1.write(r.content)
187
188 def initialize_RI(CLOUDNAME):
189     """ Create the directory structure needed by the remote-installer """
190     dirs = (
191         RI_DIR,
192         RI_DIR+'/certificates',
193         RI_DIR+'/images',
194         RI_DIR+'/installations',
195         RI_DIR+'/user-configs',
196         RI_DIR+'/user-configs/'+CLOUDNAME
197     )
198     for dir in dirs:
199         if not os.path.isdir(dir):
200             print('mkdir '+dir)
201             os.mkdir(dir)
202
203 def start_RI(client):
204     """
205     Start the remote-installer container (assumed to already be built somewhere).
206     Before starting, make sure the certificates directory is populated.  If not,
207     generate some self-signed certificates.
208     """
209     # If needed, create certificates (11 files) in RI_DIR/certificates
210     if not os.path.exists(CERT_DIR+'/clientcert.pem') or not os.path.exists(CERT_DIR+'/clientkey.pem'):
211         print('Generating some self-signed certificates.')
212         script = WORKDIR + '/gencerts.sh'
213         cmd = 'bash %s %s' % (script, RI_DIR+'/certificates')
214         print('os.system('+cmd+')')
215         os.system(cmd)
216
217     print('Starting %s.' % RI_NAME)
218     env = {
219         'API_PORT': API_PORT, 'HOST_ADDR': HOST_IP, 'HTTPS_PORT': HTTPS_PORT,
220         'PW': ADMIN_PASSWD, 'SSH_PORT': 22222
221     }
222     vols = {
223         EXTERNALROOT+RI_DIR: {'bind': '/opt/remoteinstaller', 'mode': 'rw'}
224     }
225     try:
226         c = client.containers.run(
227             image=RI_IMAGE,
228             name=RI_NAME,
229             network_mode=NETWORK,
230             environment=env,
231             volumes=vols,
232             detach=True,
233             remove=True,
234             privileged=True
235         )
236
237         # Wait 5 minutes for it to be running
238         n = 0
239         while c.status != 'running' and n < 10:
240             time.sleep(30)
241             c.reload()
242             n = n + 1
243         if c.status != 'running' and n >= 10:
244             print('Container took to long to start!')
245             sys.exit(1)
246         return c
247
248     except docker.errors.ImageNotFound as ex:
249         # If the specified image does not exist.
250         print(ex)
251         sys.exit(1)
252
253     except docker.errors.APIError as ex:
254         # If the server returns an error.
255         print(ex)
256         sys.exit(1)
257
258     except:
259         print('other error!')
260         sys.exit(1)