Update blueprint, workflows and instructions
[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
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
47 def start(ds, **kwargs):
48     # Read the user input from the POST
49     yaml = read_yaml(WORKDIR + '/INPUT.yaml')
50     REC_ISO_IMAGE_NAME        = yaml['iso_primary']
51     REC_PROVISIONING_ISO_NAME = yaml['iso_secondary']
52     INPUT_YAML_URL            = yaml['input_yaml']
53     HOST_IP                   = yaml['rc_host']
54     CLOUDNAME                 = 'CL-'+POD.POD
55     ISO                       = '%s/images/install-%s.iso' % (RI_DIR, POD.POD)
56     BOOTISO                   = '%s/images/bootcd-%s.iso'  % (RI_DIR, POD.POD)
57     USERCONF                  = '%s/user-configs/%s/user_config.yaml' % (RI_DIR, CLOUDNAME)
58
59     print('-----------------------------------------------------------------------------------------------')
60     print('                      POD is '+POD.POD)
61     print('                CLOUDNAME is '+CLOUDNAME)
62     print('                  WORKDIR is '+WORKDIR)
63     print('                  HOST_IP is '+HOST_IP)
64     print('             EXTERNALROOT is '+EXTERNALROOT)
65     print('       REC_ISO_IMAGE_NAME is '+REC_ISO_IMAGE_NAME)
66     print('REC_PROVISIONING_ISO_NAME is '+REC_PROVISIONING_ISO_NAME)
67     print('           INPUT_YAML_URL is '+INPUT_YAML_URL)
68     print('                      ISO is '+ISO)
69     print('                  BOOTISO is '+BOOTISO)
70     print('                 USERCONF is '+USERCONF)
71     print('-----------------------------------------------------------------------------------------------')
72
73     # Setup RI_DIR
74     initialize_RI(CLOUDNAME)
75
76     # Fetch the three files into WORKDIR
77     fetchURL(REC_ISO_IMAGE_NAME,        WORKDIR + '/install.iso');
78     fetchURL(REC_PROVISIONING_ISO_NAME, WORKDIR + '/bootcd.iso');
79     fetchURL(INPUT_YAML_URL,            WORKDIR + '/user_config.yaml');
80
81     # Link files to RI_DIR with unique names
82     os.link(WORKDIR + '/install.iso', ISO)
83     os.link(WORKDIR + '/bootcd.iso', BOOTISO)
84     os.link(WORKDIR + '/user_config.yaml', USERCONF)
85     PWFILE = '%s/user-configs/%s/admin_passwd' % (RI_DIR, CLOUDNAME)
86     with open(PWFILE, "w") as f:
87         f.write(ADMIN_PASSWD + '\n')
88
89     # Start the remote_installer
90     client = docker.from_env()
91     namefilt = { 'name': RI_NAME }
92     ri = client.containers.list(filters=namefilt)
93     if len(ri) == 0:
94         print(RI_NAME + ' is not running.')
95         c = start_RI(client)
96
97     else:
98         print(RI_NAME + ' is running.')
99         c = ri[0]
100
101     # Send request to remote_installer
102     id = send_request(HOST_IP, CLOUDNAME, ISO, BOOTISO)
103
104     # Wait up to WAIT_TIME minutes for completion
105     if wait_for_completion(HOST_IP, id, WAIT_TIME):
106         print('Installation failed after %d minutes.' % (WAIT_TIME))
107         sys.exit(1)
108
109     # Remove the ISOs?
110     if REMOVE_ISO:
111         for iso in (WORKDIR + '/install.iso', ISO, WORKDIR + '/bootcd.iso', BOOTISO):
112             os.unlink(iso)
113
114     # Done!
115     print('Installation complete!')
116     # sys.exit(0)  Don't exit as this will cause the task to fail!
117     return 'Complete.'
118
119 def read_yaml(input_file):
120     print('Reading '+input_file+' ...')
121     with open(input_file, 'r') as stream:
122         try:
123             return yaml.safe_load(stream)
124         except yaml.YAMLError as exc:
125             print(exc)
126             sys.exit(1)
127
128 def send_request(ri_ip, CLOUDNAME, ISO, BOOTISO):
129     URL     = 'https://%s:%d/v1/installations' % (ri_ip, API_PORT)
130     print('Sending request to '+URL+' ...')
131     headers = {'Content-type': 'application/json'}
132     content = {
133         'cloud-name': CLOUDNAME,
134         'iso': os.path.basename(ISO),
135         'provisioning-iso': os.path.basename(BOOTISO)
136     }
137     certs    = (CERT_DIR+'/clientcert.pem', CERT_DIR+'/clientkey.pem')
138     response = requests.post(URL, json=content, headers=headers, cert=certs, verify=False)
139     print(response)
140     return response.json().get('uuid')
141
142 def wait_for_completion(ri_ip, id, ntimes):
143     """
144     Wait (up to ntimes minutes) for the remote_installer to finish.
145     Any status other than 'completed' is considered a failure.
146     """
147     status = 'ongoing'
148     URL    = 'https://%s:%d/v1/installations/%s/state' % (ri_ip, API_PORT, id)
149     certs  = (CERT_DIR+'/clientcert.pem', CERT_DIR+'/clientkey.pem')
150     while status == 'ongoing' and ntimes > 0:
151         time.sleep(60)
152         response = requests.get(URL, cert=certs, verify=False)
153         j = response.json()
154         t = (
155             datetime.datetime.now().strftime('%x %X'),
156             str(j.get('status')),
157             str(j.get('percentage')),
158             str(j.get('description'))
159         )
160         print('%s: Status is %s (%s) %s' % t)
161         status = j.get('status')
162         ntimes = ntimes - 1
163     return status != 'completed'
164
165 def fetchURL(url, dest):
166     print('Fetching '+url+' ...')
167     r = requests.get(url)
168     with open(dest, 'wb') as f1:
169         f1.write(r.content)
170
171 def initialize_RI(CLOUDNAME):
172     """ Create the directory structure needed by the remote-installer """
173     dirs = (
174         RI_DIR,
175         RI_DIR+'/certificates',
176         RI_DIR+'/images',
177         RI_DIR+'/installations',
178         RI_DIR+'/user-configs',
179         RI_DIR+'/user-configs/'+CLOUDNAME
180     )
181     for dir in dirs:
182         if not os.path.isdir(dir):
183             print('mkdir '+dir)
184             os.mkdir(dir)
185
186 def start_RI(client):
187     """
188     Start the remote-installer container (assumed to already be built somewhere).
189     Before starting, make sure the certificates directory is populated.  If not,
190     generate some self-signed certificates.
191     """
192     # If needed, create certificates (11 files) in RI_DIR/certificates
193     if not os.path.exists(CERT_DIR+'/clientcert.pem') or not os.path.exists(CERT_DIR+'/clientkey.pem'):
194         print('Generating some self-signed certificates.')
195         script = WORKDIR + '/gencerts.sh'
196         cmd = 'bash %s %s' % (script, RI_DIR+'/certificates')
197         print('os.system('+cmd+')')
198         os.system(cmd)
199
200     print('Starting %s.' % RI_NAME)
201     env = {
202         'API_PORT': API_PORT, 'HOST_ADDR': HOST_IP, 'HTTPS_PORT': HTTPS_PORT,
203         'PW': ADMIN_PASSWD, 'SSH_PORT': 22222
204     }
205     vols = {
206         EXTERNALROOT+RI_DIR: {'bind': '/opt/remoteinstaller', 'mode': 'rw'}
207     }
208     try:
209         c = client.containers.run(
210             image=RI_IMAGE,
211             name=RI_NAME,
212             network_mode=NETWORK,
213             environment=env,
214             volumes=vols,
215             detach=True,
216             remove=True,
217             privileged=True
218         )
219
220         # Wait 5 minutes for it to be running
221         n = 0
222         while c.status != 'running' and n < 10:
223             time.sleep(30)
224             c.reload()
225             n = n + 1
226         if c.status != 'running' and n >= 10:
227             print('Container took to long to start!')
228             sys.exit(1)
229         return c
230
231     except docker.errors.ImageNotFound as ex:
232         # If the specified image does not exist.
233         print(ex)
234         sys.exit(1)
235
236     except docker.errors.APIError as ex:
237         # If the server returns an error.
238         print(ex)
239         sys.exit(1)
240
241     except:
242         print('other error!')
243         sys.exit(1)