Add openbmc-sfw command to let users do a code update

Signed-off-by: Charles P. Hofer <charles.hofer@ibm.com>
diff --git a/openbmc-sfw b/openbmc-sfw
index 5e95473..04304bb 100755
--- a/openbmc-sfw
+++ b/openbmc-sfw
@@ -2,6 +2,8 @@
 
 import argparse
 import requests
+import tarfile
+import time
 import json
 
 import urllib3
@@ -44,7 +46,7 @@
         return j['data']
 
     def upload_image(self, image):
-        
+
         data = open(image,'rb').read()
         r = self.session.post(self.url + "/upload/image",
                               data=data,
@@ -57,16 +59,81 @@
         return j['data']
 
     def activate_image(self, image_id):
-        r = self.session.put(self.url + "/xyz/openbmc_project/software/" + image_id + "/attr/RequestedActivation", 
+        r = self.session.put(self.url + "/xyz/openbmc_project/software/" + image_id + "/attr/RequestedActivation",
                              json={'data': 'xyz.openbmc_project.Software.Activation.RequestedActivations.Active'},
                              verify=False)
-        
+
         j = r.json()
         if j['status'] != 'ok':
             raise Exception("Failed to activate image " + image_id + ": \n" + r.text)
 
         return j['data']
 
+    def update_auto(self, image, reboot):
+        image_version = self.__get_image_version(image)
+        self.upload_image(image)
+        image_id = self.__wait_for_image_id(image_version)
+        self.activate_image(image_id)
+        self.__wait_for_activation(image_id)
+        if reboot:
+            self.reboot()
+
+    def reboot(self):
+        r = self.session.post(
+            self.url + '/org/openbmc/control/bmc0/action/warmReset',
+            headers={'Content-Type': 'application/json'},
+            data='{"data":[]}',
+            verify=False)
+
+        j = r.json()
+        if j['status'] != 'ok':
+            raise Exception("Failed to reboot BMC:\n" + r.text)
+
+    def __get_image_version(self, image_file_path):
+        # Open up the manfest file.
+        image_tar = tarfile.open(name=image_file_path, mode='r')
+        manifest = image_tar.extractfile('MANIFEST')
+
+        # Find version line.
+        for line in manifest:
+            if line.startswith('version='):
+                manifest.close()
+                image_tar.close()
+                return line.split('version=', 1)[1].strip()
+
+        # If we didn't find the version line, print an error and return false.
+        manifest.close()
+        image_tar.close()
+        raise Exception("Could not find version line in image manifest")
+
+    def __wait_for_image_id(self, image_version):
+        # Try 8 times, once every 15 seconds.
+        for attempt in range(8):
+            software = self.list_sfw()
+            # Look for our the image with the given version in software
+            for path in software:
+                image = self.get_image(path)
+                if image_version == image['Version']:
+                    return path.split('/')[-1]
+            time.sleep(15)
+        return False
+
+    def __wait_for_activation(self, image_id):
+        # Keep waiting until the image is active or activation fails.
+        active = False
+        while not active:
+            image = self.get_image("/xyz/openbmc_project/software/" + image_id)
+            if 'xyz.openbmc_project.Software.Activation.Activations.Active' \
+                    == image['Activation']:
+                print 'Activation Progress: 100%'
+                active = True
+            elif 'xyz.openbmc_project.Software.Activation.Activations.Activating' \
+                    == image['Activation']:
+                print 'Activation Progress: ' + str(image['Progress']) + '%'
+            else:
+                raise Exception("Image activation failed. The BMC has set " \
+                    + "the'Activation' property to " + image['Activation'])
+            time.sleep(15)
 
 def do_list_sfw(args):
     s = BMC(server=args.server)
@@ -74,7 +141,7 @@
         info = s.get_image(e)
         print(e)
         print json.dumps(info, indent=4)
-        
+
 def do_view_image(args):
     s = BMC(server=args.server)
     print json.dumps(s.get_image(args.image), indent=4)
@@ -87,6 +154,10 @@
     s = BMC(server=args.server)
     s.activate_image(args.image_id)
 
+def do_update_auto(args):
+    s = BMC(server=args.server)
+    s.update_auto(args.image, args.reboot)
+
 parser = argparse.ArgumentParser()
 parser.add_argument('--server', help='hostname or IP of BMC', type=str,
                     required=True)
@@ -107,6 +178,24 @@
 image_activate.add_argument('image_id', help='The image id to activate')
 image_activate.set_defaults(func=do_activate_image)
 
+image_update_auto = subparsers.add_parser('update_auto', help='Upload and '
+    + 'activate an image, and then reboot the BMC to apply the update '
+    + 'if necessary')
+image_update_auto.add_argument('image', help='The image to update to')
+image_update_auto.add_argument(
+    '--reboot',
+    dest='reboot',
+    action='store_true',
+    default=False,
+    help='Set if the BMC should reboot after the update')
+image_update_auto.add_argument(
+    '--no-reboot',
+    dest='reboot',
+    action='store_false',
+    default=False,
+    help='Setif the BMC should not reboot after the update')
+image_update_auto.set_defaults(func=do_update_auto)
+
 args = parser.parse_args()
 
 if 'func' in args: