summaryrefslogtreecommitdiffstats
path: root/docs/labs/lab06-provisioning/mlag_issu.py
blob: 307d4183502a2b05f09b0679816e564e19544182 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#!/usr/bin/env python3
#
# python3 mlag_issu <upgrade inventory file> <MLAG peer to upgrade: 'peer1' or 'peer2'>"
#
# # Example of upgrade inventory file (YAML)
# cvp_hosts:
#     - 192.168.0.191
#     - 192.168.0.192
#     - 192.168.0.193
# cvp_username: cvpadmin
# target_eos_version: 4.25.4M
# target_terminattr_version: 1.13.6
# mlag_couples:
#     - peer1: leaf101-1
#       peer2: leaf101-2
#     - peer1: leaf102-1
#       peer2: leaf102-2
#
# Note: upgrades are performed in parallel

import sys
import time
import string
import random
from getpass import getpass
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from datetime import datetime
from cvprac.cvp_client import CvpClient
from cvprac.cvp_client_errors import CvpLoginError, CvpApiError
from pprint import pprint
from operator import itemgetter
import yaml

class CvpDeviceUpgrader(object):
    def __init__(self, hosts, username, password):
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
        self.cvp_hosts = hosts
        self.cvp_user = username
        self.cvp_password = password
        self.session = self._open_cvp_session()

    def _open_cvp_session(self):
        try:
            client = CvpClient()
            client.connect(
                nodes=self.cvp_hosts,
                username=self.cvp_user,
                password=self.cvp_password,
                request_timeout=300,
                connect_timeout=30
            )
            return(client)
        except CvpLoginError as e:
            print(f"Cannot connect to CVP API: {e}")
            exit()

    def create_mlag_issu_change_control(self, taskIDs, deviceIDs):
        cc_id = f"CC_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
        pre_upgrade_stage = {'stage': [{
            'id': f"preU_{cc_id}",
            'name': 'pre_upgrade',
            'stage_row':[{'stage': [{
                'id': ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(9)),
                'action': {
                    'name': 'mlaghealthcheck',
                    'timeout': 0,
                    'args': {
                        'DeviceID': device_id
                    }
                }
            } for device_id in deviceIDs]}]
        }]}
        upgrade_stage = {'stage': [{
            'id': f"U_{cc_id}",
            'name': 'upgrade',
            'stage_row': [{'stage': [{
                'id': task_id,
                'action': {
                    'name': 'task',
                    'args': {
                        'TaskID': task_id
                    }
                }
            } for task_id in taskIDs]}]
        }]}
        post_upgrade_stage = {'stage': [{
            'id': f"postU_{cc_id}",
            'name': 'post_upgrade',
            'stage_row': [{'stage': [{
                'id': ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(9)),
                'action': {
                    'name': 'mlaghealthcheck',
                    'timeout': 0,
                    'args': {
                        'DeviceID': device_id
                    }
                }
            } for device_id in deviceIDs]}]
        }]}
        cc_data = {'config': {
            'id': cc_id,
            'name': f"Change Control {cc_id}",
            'root_stage': {
                'id': 'root',
                'name': f"Change Control {cc_id} root",
                'stage_row': [pre_upgrade_stage, upgrade_stage, post_upgrade_stage],
            }
        }}
        try:
            res = self.session.post('/api/v3/services/ccapi.ChangeControl/Update',
                data=cc_data,
                timeout=self.session.api.request_timeout
            )
        except Exception as e:
            print(str(e))
            return(None)
        print(f"Change control {res[0]['id']} created at {res[0]['update_timestamp']}")
        return(res[0]['id'])

    def get_mlag_issu_change_control_logs(self, ccID, startTime):
        end_time = int(time.time() * 1000)
        cc_logs_data = {'category': 'ChangeControl',
            'objectKey': ccID,
            'dataSize': 15000,
            'startTime': startTime,
            'endTime': end_time
        }
        logs = self.session.post('/cvpservice/audit/getLogs.do',
            data=cc_logs_data,
            timeout=self.session.api.request_timeout
        )
        for log in sorted(logs['data'], key=itemgetter('dateTimeInLongFormat')):
            if log['subObjectName'] and 'Command(s)' not in log['activity']:
                log_date = datetime.fromtimestamp(log['dateTimeInLongFormat']/1000)
                print(f"{log_date} {log['subObjectName']}: {log['activity']}")
        return(end_time + 1)

    def run_mlag_issu_change_control(self, ccID):
        print(f"Automatic approval of change control {ccID}")
        self.session.api.approve_change_control(ccID, datetime.utcnow().isoformat() + 'Z')
        time.sleep(2)
        print(f"Starting the execution of change control {ccID}")
        start_time = int(time.time() * 1000)
        self.session.api.execute_change_controls([ccID])
        time.sleep(2)
        cc_status = self.session.api.get_change_control_status(ccID)[0]['status']
        start_time = self.get_mlag_issu_change_control_logs(ccID, start_time)
        while cc_status['state'] == 'Running':
            time.sleep(30)
            cc_status = self.session.api.get_change_control_status(ccID)[0]['status']
            start_time = self.get_mlag_issu_change_control_logs(ccID, start_time)
        print(f"Change control {ccID} final status: {cc_status['state']}")
        if cc_status['error']:
            print(f"Change control {ccID} had the following errors: {cc_status['error']}")
        else:
            print(f"Change control {ccID} completed without errors")

def main():
    if len(sys.argv) != 3:
        print(f"Usage: python3 {sys.argv[0]} <input file path> <MLAG peer to upgrade: peer1/peer2>")
        exit()
    try:
        with open(sys.argv[1], 'r') as yf:
            params = yaml.safe_load(yf)
    except Exception as e:
        print(e)
        exit()
    cvp_password = getpass(prompt=f"CVP password for user {params['cvp_username']}: ")
    cvpdu = CvpDeviceUpgrader(
        hosts=params['cvp_hosts'],
        username=params['cvp_username'],
        password=cvp_password
    )
    image_bundle = None
    for bundle in cvpdu.session.api.get_image_bundles()['data']:
        eos_match = False
        terminattr_match = False
        for img in bundle['imageIds']:
            if params['target_eos_version'] in img:
                eos_match = True
            elif params['target_terminattr_version'] in img:
                terminattr_match = True
        if eos_match and terminattr_match:
            image_bundle = bundle
            break
    if image_bundle is None:
        print(f"Cannot find an image bundle with EOS {params['target_eos_version']} and TerminAttr {params['target_terminattr_version']}")
        exit()
    hostnames = [couple[sys.argv[2]] for couple in params['mlag_couples']]
    devices_to_upgrade = list()
    inventory = cvpdu.session.api.get_inventory()
    for hostname in hostnames:
        provisioned = False
        for dev in inventory:
            if dev['hostname'] == hostname:
                provisioned = True
                devices_to_upgrade.append(dev)
                break
        if not provisioned:
            print(f"Device with hostname {hostname} is not provisioned in CVP")
    if not devices_to_upgrade:
        print('none of the mentioned devices is provisioned in CVP')
        exit()
    print(f"Devices to upgrade: {', '.join([dev['hostname'] for dev in devices_to_upgrade])}")
    task_ids = list()
    for device in devices_to_upgrade:
        response = cvpdu.session.api.apply_image_to_device(image_bundle, device)['data']
        if response['status'] == 'success':
            task_ids.extend(response['taskIds'])
    device_ids = [device['serialNumber'] for device in devices_to_upgrade]
    cc_id = cvpdu.create_mlag_issu_change_control(task_ids, device_ids)
    if cc_id is None:
        print('Failed to create the MLAG ISSU change control')
        exit()
    time.sleep(2)
    cvpdu.run_mlag_issu_change_control(cc_id)

if __name__ == '__main__':
    main()