Fix 2 problems with create workflow
[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 wait_for_completion(ri_ip, id, ntimes):
146     """
147     Wait (up to ntimes minutes) for the remote_installer to finish.
148     Any status other than 'completed' is considered a failure.
149     """
150     status = 'ongoing'
151     URL    = 'https://%s:%d/v1/installations/%s/state' % (ri_ip, API_PORT, id)
152     certs  = (CERT_DIR+'/clientcert.pem', CERT_DIR+'/clientkey.pem')
153     while status == 'ongoing' and ntimes > 0:
154         time.sleep(60)
155         response = requests.get(URL, cert=certs, verify=False)
156         j = response.json()
157         t = (
158             datetime.datetime.now().strftime('%x %X'),
159             str(j.get('status')),
160             str(j.get('percentage')),
161             str(j.get('description'))
162         )
163         print('%s: Status is %s (%s) %s' % t)
164         status = j.get('status')
165         ntimes = ntimes - 1
166     return status != 'completed'
167
168 def fetchURL(url, dest):
169     print('Fetching '+url+' ...')
170     r = requests.get(url)
171     with open(dest, 'wb') as f1:
172         f1.write(r.content)
173
174 def initialize_RI(CLOUDNAME):
175     """ Create the directory structure needed by the remote-installer """
176     dirs = (
177         RI_DIR,
178         RI_DIR+'/certificates',
179         RI_DIR+'/images',
180         RI_DIR+'/installations',
181         RI_DIR+'/user-configs',
182         RI_DIR+'/user-configs/'+CLOUDNAME
183     )
184     for dir in dirs:
185         if not os.path.isdir(dir):
186             print('mkdir '+dir)
187             os.mkdir(dir)
188
189 def start_RI(client):
190     """
191     Start the remote-installer container (assumed to already be built somewhere).
192     Before starting, make sure the certificates directory is populated.  If not,
193     generate some self-signed certificates.
194     """
195     # If needed, create certificates (11 files) in RI_DIR/certificates
196     if not os.path.exists(CERT_DIR+'/clientcert.pem') or not os.path.exists(CERT_DIR+'/clientkey.pem'):
197         print('Generating some self-signed certificates.')
198         script = WORKDIR + '/gencerts.sh'
199         cmd = 'bash %s %s' % (script, RI_DIR+'/certificates')
200         print('os.system('+cmd+')')
201         os.system(cmd)
202
203     print('Starting %s.' % RI_NAME)
204     env = {
205         'API_PORT': API_PORT, 'HOST_ADDR': HOST_IP, 'HTTPS_PORT': HTTPS_PORT,
206         'PW': ADMIN_PASSWD, 'SSH_PORT': 22222
207     }
208     vols = {
209         EXTERNALROOT+RI_DIR: {'bind': '/opt/remoteinstaller', 'mode': 'rw'}
210     }
211     try:
212         c = client.containers.run(
213             image=RI_IMAGE,
214             name=RI_NAME,
215             network_mode=NETWORK,
216             environment=env,
217             volumes=vols,
218             detach=True,
219             remove=True,
220             privileged=True
221         )
222
223         # Wait 5 minutes for it to be running
224         n = 0
225         while c.status != 'running' and n < 10:
226             time.sleep(30)
227             c.reload()
228             n = n + 1
229         if c.status != 'running' and n >= 10:
230             print('Container took to long to start!')
231             sys.exit(1)
232         return c
233
234     except docker.errors.ImageNotFound as ex:
235         # If the specified image does not exist.
236         print(ex)
237         sys.exit(1)
238
239     except docker.errors.APIError as ex:
240         # If the server returns an error.
241         print(ex)
242         sys.exit(1)
243
244     except:
245         print('other error!')
246         sys.exit(1)