Shutdown Source VM (PAUSED) on successfull migration + blackened all .py files
This commit is contained in:
		
					parent
					
						
							
								29e938dc74
							
						
					
				
			
			
				commit
				
					
						9bdf4d2180
					
				
			
		
					 31 changed files with 1307 additions and 638 deletions
				
			
		
							
								
								
									
										57
									
								
								setup.py
									
										
									
									
									
								
							
							
						
						
									
										57
									
								
								setup.py
									
										
									
									
									
								
							| 
						 | 
					@ -7,41 +7,48 @@ with open("README.md", "r") as fh:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    import ucloud.version
 | 
					    import ucloud.version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    version = ucloud.version.VERSION
 | 
					    version = ucloud.version.VERSION
 | 
				
			||||||
except:
 | 
					except:
 | 
				
			||||||
    import subprocess
 | 
					    import subprocess
 | 
				
			||||||
    c = subprocess.check_output(['git', 'describe'])
 | 
					
 | 
				
			||||||
 | 
					    c = subprocess.check_output(["git", "describe"])
 | 
				
			||||||
    version = c.decode("utf-8").strip()
 | 
					    version = c.decode("utf-8").strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
setup(name='ucloud',
 | 
					setup(
 | 
				
			||||||
 | 
					    name="ucloud",
 | 
				
			||||||
    version=version,
 | 
					    version=version,
 | 
				
			||||||
      description='All ucloud server components.',
 | 
					    description="All ucloud server components.",
 | 
				
			||||||
      url='https://code.ungleich.ch/ucloud/ucloud',
 | 
					    url="https://code.ungleich.ch/ucloud/ucloud",
 | 
				
			||||||
    long_description=long_description,
 | 
					    long_description=long_description,
 | 
				
			||||||
      long_description_content_type='text/markdown',
 | 
					    long_description_content_type="text/markdown",
 | 
				
			||||||
    classifiers=[
 | 
					    classifiers=[
 | 
				
			||||||
          'Development Status :: 3 - Alpha',
 | 
					        "Development Status :: 3 - Alpha",
 | 
				
			||||||
          'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
 | 
					        "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
 | 
				
			||||||
          'Programming Language :: Python :: 3'
 | 
					        "Programming Language :: Python :: 3",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
      author='ungleich',
 | 
					    author="ungleich",
 | 
				
			||||||
      author_email='technik@ungleich.ch',
 | 
					    author_email="technik@ungleich.ch",
 | 
				
			||||||
    packages=find_packages(),
 | 
					    packages=find_packages(),
 | 
				
			||||||
    install_requires=[
 | 
					    install_requires=[
 | 
				
			||||||
          'requests',
 | 
					        "requests",
 | 
				
			||||||
          'Flask>=1.1.1',
 | 
					        "Flask>=1.1.1",
 | 
				
			||||||
          'flask-restful',
 | 
					        "flask-restful",
 | 
				
			||||||
          'bitmath',
 | 
					        "bitmath",
 | 
				
			||||||
          'pyotp',
 | 
					        "pyotp",
 | 
				
			||||||
          'sshtunnel',
 | 
					        "sshtunnel",
 | 
				
			||||||
          'sphinx',
 | 
					        "sphinx",
 | 
				
			||||||
          'pynetbox',
 | 
					        "pynetbox",
 | 
				
			||||||
          'colorama',
 | 
					        "colorama",
 | 
				
			||||||
          'sphinx-rtd-theme',
 | 
					        "sphinx-rtd-theme",
 | 
				
			||||||
          'etcd3 @ https://github.com/kragniz/python-etcd3/tarball/master#egg=etcd3',
 | 
					        "etcd3 @ https://github.com/kragniz/python-etcd3/tarball/master#egg=etcd3",
 | 
				
			||||||
          'werkzeug', 'marshmallow'
 | 
					        "werkzeug",
 | 
				
			||||||
 | 
					        "marshmallow",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
      scripts=['scripts/ucloud'],
 | 
					    scripts=["scripts/ucloud"],
 | 
				
			||||||
      data_files=[(os.path.expanduser('~/ucloud/'), ['conf/ucloud.conf'])],
 | 
					    data_files=[
 | 
				
			||||||
      zip_safe=False)
 | 
					        (os.path.expanduser("~/ucloud/"), ["conf/ucloud.conf"])
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    zip_safe=False,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,12 +20,16 @@ class Field:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_valid(self):
 | 
					    def is_valid(self):
 | 
				
			||||||
        if self.value == KeyError:
 | 
					        if self.value == KeyError:
 | 
				
			||||||
            self.add_error("'{}' field is a required field".format(self.name))
 | 
					            self.add_error(
 | 
				
			||||||
 | 
					                "'{}' field is a required field".format(self.name)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            if isinstance(self.value, Optional):
 | 
					            if isinstance(self.value, Optional):
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
            elif not isinstance(self.value, self.type):
 | 
					            elif not isinstance(self.value, self.type):
 | 
				
			||||||
                self.add_error("Incorrect Type for '{}' field".format(self.name))
 | 
					                self.add_error(
 | 
				
			||||||
 | 
					                    "Incorrect Type for '{}' field".format(self.name)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.validation()
 | 
					                self.validation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,6 +53,10 @@ class VmUUIDField(Field):
 | 
				
			||||||
        self.validation = self.vm_uuid_validation
 | 
					        self.validation = self.vm_uuid_validation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def vm_uuid_validation(self):
 | 
					    def vm_uuid_validation(self):
 | 
				
			||||||
        r = shared.etcd_client.get(os.path.join(settings['etcd']['vm_prefix'], self.uuid))
 | 
					        r = shared.etcd_client.get(
 | 
				
			||||||
 | 
					            os.path.join(settings["etcd"]["vm_prefix"], self.uuid)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        if not r:
 | 
					        if not r:
 | 
				
			||||||
            self.add_error("VM with uuid {} does not exists".format(self.uuid))
 | 
					            self.add_error(
 | 
				
			||||||
 | 
					                "VM with uuid {} does not exists".format(self.uuid)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,4 +14,7 @@ data = {
 | 
				
			||||||
    "attributes": {"list": [], "key": [], "pool": "images"},
 | 
					    "attributes": {"list": [], "key": [], "pool": "images"},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
shared.etcd_client.put(os.path.join(settings['etcd']['image_store_prefix'], uuid4().hex), json.dumps(data))
 | 
					shared.etcd_client.put(
 | 
				
			||||||
 | 
					    os.path.join(settings["etcd"]["image_store_prefix"], uuid4().hex),
 | 
				
			||||||
 | 
					    json.dumps(data),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,21 +16,23 @@ logger = logging.getLogger(__name__)
 | 
				
			||||||
def check_otp(name, realm, token):
 | 
					def check_otp(name, realm, token):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        data = {
 | 
					        data = {
 | 
				
			||||||
            "auth_name": settings['otp']['auth_name'],
 | 
					            "auth_name": settings["otp"]["auth_name"],
 | 
				
			||||||
            "auth_token": TOTP(settings['otp']['auth_seed']).now(),
 | 
					            "auth_token": TOTP(settings["otp"]["auth_seed"]).now(),
 | 
				
			||||||
            "auth_realm": settings['otp']['auth_realm'],
 | 
					            "auth_realm": settings["otp"]["auth_realm"],
 | 
				
			||||||
            "name": name,
 | 
					            "name": name,
 | 
				
			||||||
            "realm": realm,
 | 
					            "realm": realm,
 | 
				
			||||||
            "token": token,
 | 
					            "token": token,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    except binascii.Error as err:
 | 
					    except binascii.Error as err:
 | 
				
			||||||
        logger.error(
 | 
					        logger.error(
 | 
				
			||||||
            "Cannot compute OTP for seed: {}".format(settings['otp']['auth_seed'])
 | 
					            "Cannot compute OTP for seed: {}".format(
 | 
				
			||||||
 | 
					                settings["otp"]["auth_seed"]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return 400
 | 
					        return 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    response = requests.post(
 | 
					    response = requests.post(
 | 
				
			||||||
        settings['otp']['verification_controller_url'], json=data
 | 
					        settings["otp"]["verification_controller_url"], json=data
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    return response.status_code
 | 
					    return response.status_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +45,8 @@ def resolve_vm_name(name, owner):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    result = next(
 | 
					    result = next(
 | 
				
			||||||
        filter(
 | 
					        filter(
 | 
				
			||||||
            lambda vm: vm.value["owner"] == owner and vm.value["name"] == name,
 | 
					            lambda vm: vm.value["owner"] == owner
 | 
				
			||||||
 | 
					            and vm.value["name"] == name,
 | 
				
			||||||
            shared.vm_pool.vms,
 | 
					            shared.vm_pool.vms,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        None,
 | 
					        None,
 | 
				
			||||||
| 
						 | 
					@ -80,18 +83,27 @@ def resolve_image_name(name, etcd_client):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        store_name, image_name = store_name_and_image_name
 | 
					        store_name, image_name = store_name_and_image_name
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        raise ValueError("Image name not in correct format i.e {store_name}:{image_name}")
 | 
					        raise ValueError(
 | 
				
			||||||
 | 
					            "Image name not in correct format i.e {store_name}:{image_name}"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    images = etcd_client.get_prefix(settings['etcd']['image_prefix'], value_in_json=True)
 | 
					    images = etcd_client.get_prefix(
 | 
				
			||||||
 | 
					        settings["etcd"]["image_prefix"], value_in_json=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Try to find image with name == image_name and store_name == store_name
 | 
					    # Try to find image with name == image_name and store_name == store_name
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        image = next(filter(lambda im: im.value['name'] == image_name
 | 
					        image = next(
 | 
				
			||||||
                            and im.value['store_name'] == store_name, images))
 | 
					            filter(
 | 
				
			||||||
 | 
					                lambda im: im.value["name"] == image_name
 | 
				
			||||||
 | 
					                and im.value["store_name"] == store_name,
 | 
				
			||||||
 | 
					                images,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except StopIteration:
 | 
					    except StopIteration:
 | 
				
			||||||
        raise KeyError("No image with name {} found.".format(name))
 | 
					        raise KeyError("No image with name {} found.".format(name))
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        image_uuid = image.key.split('/')[-1]
 | 
					        image_uuid = image.key.split("/")[-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return image_uuid
 | 
					    return image_uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -100,7 +112,9 @@ def random_bytes(num=6):
 | 
				
			||||||
    return [random.randrange(256) for _ in range(num)]
 | 
					    return [random.randrange(256) for _ in range(num)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_mac(uaa=False, multicast=False, oui=None, separator=':', byte_fmt='%02x'):
 | 
					def generate_mac(
 | 
				
			||||||
 | 
					    uaa=False, multicast=False, oui=None, separator=":", byte_fmt="%02x"
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
    mac = random_bytes()
 | 
					    mac = random_bytes()
 | 
				
			||||||
    if oui:
 | 
					    if oui:
 | 
				
			||||||
        if type(oui) == str:
 | 
					        if type(oui) == str:
 | 
				
			||||||
| 
						 | 
					@ -131,7 +145,9 @@ def get_ip_addr(mac_address, device):
 | 
				
			||||||
          and is connected/neighbor of arg:device
 | 
					          and is connected/neighbor of arg:device
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        output = sp.check_output(['ip', '-6', 'neigh', 'show', 'dev', device], stderr=sp.PIPE)
 | 
					        output = sp.check_output(
 | 
				
			||||||
 | 
					            ["ip", "-6", "neigh", "show", "dev", device], stderr=sp.PIPE
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except sp.CalledProcessError:
 | 
					    except sp.CalledProcessError:
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ def handle_exception(e):
 | 
				
			||||||
        return e
 | 
					        return e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # now you're handling non-HTTP exceptions only
 | 
					    # now you're handling non-HTTP exceptions only
 | 
				
			||||||
    return {'message': 'Server Error'}, 500
 | 
					    return {"message": "Server Error"}, 500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CreateVM(Resource):
 | 
					class CreateVM(Resource):
 | 
				
			||||||
| 
						 | 
					@ -55,7 +55,7 @@ class CreateVM(Resource):
 | 
				
			||||||
        validator = schemas.CreateVMSchema(data)
 | 
					        validator = schemas.CreateVMSchema(data)
 | 
				
			||||||
        if validator.is_valid():
 | 
					        if validator.is_valid():
 | 
				
			||||||
            vm_uuid = uuid4().hex
 | 
					            vm_uuid = uuid4().hex
 | 
				
			||||||
            vm_key = join_path(settings['etcd']['vm_prefix'], vm_uuid)
 | 
					            vm_key = join_path(settings["etcd"]["vm_prefix"], vm_uuid)
 | 
				
			||||||
            specs = {
 | 
					            specs = {
 | 
				
			||||||
                "cpu": validator.specs["cpu"],
 | 
					                "cpu": validator.specs["cpu"],
 | 
				
			||||||
                "ram": validator.specs["ram"],
 | 
					                "ram": validator.specs["ram"],
 | 
				
			||||||
| 
						 | 
					@ -63,8 +63,12 @@ class CreateVM(Resource):
 | 
				
			||||||
                "hdd": validator.specs["hdd"],
 | 
					                "hdd": validator.specs["hdd"],
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            macs = [generate_mac() for _ in range(len(data["network"]))]
 | 
					            macs = [generate_mac() for _ in range(len(data["network"]))]
 | 
				
			||||||
            tap_ids = [counters.increment_etcd_counter(shared.etcd_client, "/v1/counter/tap")
 | 
					            tap_ids = [
 | 
				
			||||||
                       for _ in range(len(data["network"]))]
 | 
					                counters.increment_etcd_counter(
 | 
				
			||||||
 | 
					                    shared.etcd_client, "/v1/counter/tap"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                for _ in range(len(data["network"]))
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
            vm_entry = {
 | 
					            vm_entry = {
 | 
				
			||||||
                "name": data["vm_name"],
 | 
					                "name": data["vm_name"],
 | 
				
			||||||
                "owner": data["name"],
 | 
					                "owner": data["name"],
 | 
				
			||||||
| 
						 | 
					@ -77,14 +81,15 @@ class CreateVM(Resource):
 | 
				
			||||||
                "vnc_socket": "",
 | 
					                "vnc_socket": "",
 | 
				
			||||||
                "network": list(zip(data["network"], macs, tap_ids)),
 | 
					                "network": list(zip(data["network"], macs, tap_ids)),
 | 
				
			||||||
                "metadata": {"ssh-keys": []},
 | 
					                "metadata": {"ssh-keys": []},
 | 
				
			||||||
                "in_migration": False
 | 
					                "in_migration": False,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            shared.etcd_client.put(vm_key, vm_entry, value_in_json=True)
 | 
					            shared.etcd_client.put(vm_key, vm_entry, value_in_json=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Create ScheduleVM Request
 | 
					            # Create ScheduleVM Request
 | 
				
			||||||
            r = RequestEntry.from_scratch(
 | 
					            r = RequestEntry.from_scratch(
 | 
				
			||||||
                type=RequestType.ScheduleVM, uuid=vm_uuid,
 | 
					                type=RequestType.ScheduleVM,
 | 
				
			||||||
                request_prefix=settings['etcd']['request_prefix']
 | 
					                uuid=vm_uuid,
 | 
				
			||||||
 | 
					                request_prefix=settings["etcd"]["request_prefix"],
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            shared.request_pool.put(r)
 | 
					            shared.request_pool.put(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,7 +104,7 @@ class VmStatus(Resource):
 | 
				
			||||||
        validator = schemas.VMStatusSchema(data)
 | 
					        validator = schemas.VMStatusSchema(data)
 | 
				
			||||||
        if validator.is_valid():
 | 
					        if validator.is_valid():
 | 
				
			||||||
            vm = shared.vm_pool.get(
 | 
					            vm = shared.vm_pool.get(
 | 
				
			||||||
                join_path(settings['etcd']['vm_prefix'], data["uuid"])
 | 
					                join_path(settings["etcd"]["vm_prefix"], data["uuid"])
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            vm_value = vm.value.copy()
 | 
					            vm_value = vm.value.copy()
 | 
				
			||||||
            vm_value["ip"] = []
 | 
					            vm_value["ip"] = []
 | 
				
			||||||
| 
						 | 
					@ -107,13 +112,15 @@ class VmStatus(Resource):
 | 
				
			||||||
                network_name, mac, tap = network_mac_and_tap
 | 
					                network_name, mac, tap = network_mac_and_tap
 | 
				
			||||||
                network = shared.etcd_client.get(
 | 
					                network = shared.etcd_client.get(
 | 
				
			||||||
                    join_path(
 | 
					                    join_path(
 | 
				
			||||||
                        settings['etcd']['network_prefix'],
 | 
					                        settings["etcd"]["network_prefix"],
 | 
				
			||||||
                        data["name"],
 | 
					                        data["name"],
 | 
				
			||||||
                        network_name,
 | 
					                        network_name,
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    value_in_json=True,
 | 
					                    value_in_json=True,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                ipv6_addr = network.value.get("ipv6").split("::")[0] + "::"
 | 
					                ipv6_addr = (
 | 
				
			||||||
 | 
					                    network.value.get("ipv6").split("::")[0] + "::"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                vm_value["ip"].append(mac2ipv6(mac, ipv6_addr))
 | 
					                vm_value["ip"].append(mac2ipv6(mac, ipv6_addr))
 | 
				
			||||||
            vm.value = vm_value
 | 
					            vm.value = vm_value
 | 
				
			||||||
            return vm.value
 | 
					            return vm.value
 | 
				
			||||||
| 
						 | 
					@ -128,7 +135,7 @@ class CreateImage(Resource):
 | 
				
			||||||
        validator = schemas.CreateImageSchema(data)
 | 
					        validator = schemas.CreateImageSchema(data)
 | 
				
			||||||
        if validator.is_valid():
 | 
					        if validator.is_valid():
 | 
				
			||||||
            file_entry = shared.etcd_client.get(
 | 
					            file_entry = shared.etcd_client.get(
 | 
				
			||||||
                join_path(settings['etcd']['file_prefix'], data["uuid"])
 | 
					                join_path(settings["etcd"]["file_prefix"], data["uuid"])
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            file_entry_value = json.loads(file_entry.value)
 | 
					            file_entry_value = json.loads(file_entry.value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,7 +148,9 @@ class CreateImage(Resource):
 | 
				
			||||||
                "visibility": "public",
 | 
					                "visibility": "public",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            shared.etcd_client.put(
 | 
					            shared.etcd_client.put(
 | 
				
			||||||
                join_path(settings['etcd']['image_prefix'], data["uuid"]),
 | 
					                join_path(
 | 
				
			||||||
 | 
					                    settings["etcd"]["image_prefix"], data["uuid"]
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
                json.dumps(image_entry_json),
 | 
					                json.dumps(image_entry_json),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -153,11 +162,9 @@ class ListPublicImages(Resource):
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def get():
 | 
					    def get():
 | 
				
			||||||
        images = shared.etcd_client.get_prefix(
 | 
					        images = shared.etcd_client.get_prefix(
 | 
				
			||||||
            settings['etcd']['image_prefix'], value_in_json=True
 | 
					            settings["etcd"]["image_prefix"], value_in_json=True
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        r = {
 | 
					        r = {"images": []}
 | 
				
			||||||
            "images": []
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        for image in images:
 | 
					        for image in images:
 | 
				
			||||||
            image_key = "{}:{}".format(
 | 
					            image_key = "{}:{}".format(
 | 
				
			||||||
                image.value["store_name"], image.value["name"]
 | 
					                image.value["store_name"], image.value["name"]
 | 
				
			||||||
| 
						 | 
					@ -176,7 +183,7 @@ class VMAction(Resource):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if validator.is_valid():
 | 
					        if validator.is_valid():
 | 
				
			||||||
            vm_entry = shared.vm_pool.get(
 | 
					            vm_entry = shared.vm_pool.get(
 | 
				
			||||||
                join_path(settings['etcd']['vm_prefix'], data["uuid"])
 | 
					                join_path(settings["etcd"]["vm_prefix"], data["uuid"])
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            action = data["action"]
 | 
					            action = data["action"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -184,13 +191,19 @@ class VMAction(Resource):
 | 
				
			||||||
                action = "schedule"
 | 
					                action = "schedule"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if action == "delete" and vm_entry.hostname == "":
 | 
					            if action == "delete" and vm_entry.hostname == "":
 | 
				
			||||||
                if shared.storage_handler.is_vm_image_exists(vm_entry.uuid):
 | 
					                if shared.storage_handler.is_vm_image_exists(
 | 
				
			||||||
                    r_status = shared.storage_handler.delete_vm_image(vm_entry.uuid)
 | 
					                    vm_entry.uuid
 | 
				
			||||||
 | 
					                ):
 | 
				
			||||||
 | 
					                    r_status = shared.storage_handler.delete_vm_image(
 | 
				
			||||||
 | 
					                        vm_entry.uuid
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    if r_status:
 | 
					                    if r_status:
 | 
				
			||||||
                        shared.etcd_client.client.delete(vm_entry.key)
 | 
					                        shared.etcd_client.client.delete(vm_entry.key)
 | 
				
			||||||
                        return {"message": "VM successfully deleted"}
 | 
					                        return {"message": "VM successfully deleted"}
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        logger.error("Some Error Occurred while deleting VM")
 | 
					                        logger.error(
 | 
				
			||||||
 | 
					                            "Some Error Occurred while deleting VM"
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
                        return {"message": "VM deletion unsuccessfull"}
 | 
					                        return {"message": "VM deletion unsuccessfull"}
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    shared.etcd_client.client.delete(vm_entry.key)
 | 
					                    shared.etcd_client.client.delete(vm_entry.key)
 | 
				
			||||||
| 
						 | 
					@ -200,10 +213,13 @@ class VMAction(Resource):
 | 
				
			||||||
                type="{}VM".format(action.title()),
 | 
					                type="{}VM".format(action.title()),
 | 
				
			||||||
                uuid=data["uuid"],
 | 
					                uuid=data["uuid"],
 | 
				
			||||||
                hostname=vm_entry.hostname,
 | 
					                hostname=vm_entry.hostname,
 | 
				
			||||||
                request_prefix=settings['etcd']['request_prefix']
 | 
					                request_prefix=settings["etcd"]["request_prefix"],
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            shared.request_pool.put(r)
 | 
					            shared.request_pool.put(r)
 | 
				
			||||||
            return {"message": "VM {} Queued".format(action.title())}, 200
 | 
					            return (
 | 
				
			||||||
 | 
					                {"message": "VM {} Queued".format(action.title())},
 | 
				
			||||||
 | 
					                200,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return validator.get_errors(), 400
 | 
					            return validator.get_errors(), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -216,15 +232,21 @@ class VMMigration(Resource):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if validator.is_valid():
 | 
					        if validator.is_valid():
 | 
				
			||||||
            vm = shared.vm_pool.get(data["uuid"])
 | 
					            vm = shared.vm_pool.get(data["uuid"])
 | 
				
			||||||
            r = RequestEntry.from_scratch(type=RequestType.InitVMMigration,
 | 
					            r = RequestEntry.from_scratch(
 | 
				
			||||||
 | 
					                type=RequestType.InitVMMigration,
 | 
				
			||||||
                uuid=vm.uuid,
 | 
					                uuid=vm.uuid,
 | 
				
			||||||
                hostname=join_path(
 | 
					                hostname=join_path(
 | 
				
			||||||
                                              settings['etcd']['host_prefix'], validator.destination.value
 | 
					                    settings["etcd"]["host_prefix"],
 | 
				
			||||||
 | 
					                    validator.destination.value,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                                          request_prefix=settings['etcd']['request_prefix'])
 | 
					                request_prefix=settings["etcd"]["request_prefix"],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            shared.request_pool.put(r)
 | 
					            shared.request_pool.put(r)
 | 
				
			||||||
            return {"message": "VM Migration Initialization Queued"}, 200
 | 
					            return (
 | 
				
			||||||
 | 
					                {"message": "VM Migration Initialization Queued"},
 | 
				
			||||||
 | 
					                200,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return validator.get_errors(), 400
 | 
					            return validator.get_errors(), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -237,10 +259,12 @@ class ListUserVM(Resource):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if validator.is_valid():
 | 
					        if validator.is_valid():
 | 
				
			||||||
            vms = shared.etcd_client.get_prefix(
 | 
					            vms = shared.etcd_client.get_prefix(
 | 
				
			||||||
                settings['etcd']['vm_prefix'], value_in_json=True
 | 
					                settings["etcd"]["vm_prefix"], value_in_json=True
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            return_vms = []
 | 
					            return_vms = []
 | 
				
			||||||
            user_vms = filter(lambda v: v.value["owner"] == data["name"], vms)
 | 
					            user_vms = filter(
 | 
				
			||||||
 | 
					                lambda v: v.value["owner"] == data["name"], vms
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            for vm in user_vms:
 | 
					            for vm in user_vms:
 | 
				
			||||||
                return_vms.append(
 | 
					                return_vms.append(
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
| 
						 | 
					@ -249,9 +273,7 @@ class ListUserVM(Resource):
 | 
				
			||||||
                        "specs": vm.value["specs"],
 | 
					                        "specs": vm.value["specs"],
 | 
				
			||||||
                        "status": vm.value["status"],
 | 
					                        "status": vm.value["status"],
 | 
				
			||||||
                        "hostname": vm.value["hostname"],
 | 
					                        "hostname": vm.value["hostname"],
 | 
				
			||||||
                        "vnc_socket": None
 | 
					                        "vnc_socket": vm.value.get("vnc_socket", None),
 | 
				
			||||||
                        if vm.value.get("vnc_socket", None) is None
 | 
					 | 
				
			||||||
                        else vm.value["vnc_socket"],
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            if return_vms:
 | 
					            if return_vms:
 | 
				
			||||||
| 
						 | 
					@ -270,11 +292,13 @@ class ListUserFiles(Resource):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if validator.is_valid():
 | 
					        if validator.is_valid():
 | 
				
			||||||
            files = shared.etcd_client.get_prefix(
 | 
					            files = shared.etcd_client.get_prefix(
 | 
				
			||||||
                settings['etcd']['file_prefix'], value_in_json=True
 | 
					                settings["etcd"]["file_prefix"], value_in_json=True
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            return_files = []
 | 
					            return_files = []
 | 
				
			||||||
            user_files = list(
 | 
					            user_files = list(
 | 
				
			||||||
                filter(lambda f: f.value["owner"] == data["name"], files)
 | 
					                filter(
 | 
				
			||||||
 | 
					                    lambda f: f.value["owner"] == data["name"], files
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            for file in user_files:
 | 
					            for file in user_files:
 | 
				
			||||||
                return_files.append(
 | 
					                return_files.append(
 | 
				
			||||||
| 
						 | 
					@ -294,14 +318,18 @@ class CreateHost(Resource):
 | 
				
			||||||
        data = request.json
 | 
					        data = request.json
 | 
				
			||||||
        validator = schemas.CreateHostSchema(data)
 | 
					        validator = schemas.CreateHostSchema(data)
 | 
				
			||||||
        if validator.is_valid():
 | 
					        if validator.is_valid():
 | 
				
			||||||
            host_key = join_path(settings['etcd']['host_prefix'], uuid4().hex)
 | 
					            host_key = join_path(
 | 
				
			||||||
 | 
					                settings["etcd"]["host_prefix"], uuid4().hex
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            host_entry = {
 | 
					            host_entry = {
 | 
				
			||||||
                "specs": data["specs"],
 | 
					                "specs": data["specs"],
 | 
				
			||||||
                "hostname": data["hostname"],
 | 
					                "hostname": data["hostname"],
 | 
				
			||||||
                "status": "DEAD",
 | 
					                "status": "DEAD",
 | 
				
			||||||
                "last_heartbeat": "",
 | 
					                "last_heartbeat": "",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            shared.etcd_client.put(host_key, host_entry, value_in_json=True)
 | 
					            shared.etcd_client.put(
 | 
				
			||||||
 | 
					                host_key, host_entry, value_in_json=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return {"message": "Host Created"}, 200
 | 
					            return {"message": "Host Created"}, 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -333,7 +361,7 @@ class GetSSHKeys(Resource):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # {user_prefix}/{realm}/{name}/key/
 | 
					                # {user_prefix}/{realm}/{name}/key/
 | 
				
			||||||
                etcd_key = join_path(
 | 
					                etcd_key = join_path(
 | 
				
			||||||
                    settings['etcd']['user_prefix'],
 | 
					                    settings["etcd"]["user_prefix"],
 | 
				
			||||||
                    data["realm"],
 | 
					                    data["realm"],
 | 
				
			||||||
                    data["name"],
 | 
					                    data["name"],
 | 
				
			||||||
                    "key",
 | 
					                    "key",
 | 
				
			||||||
| 
						 | 
					@ -343,25 +371,30 @@ class GetSSHKeys(Resource):
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                keys = {
 | 
					                keys = {
 | 
				
			||||||
                    key.key.split("/")[-1]: key.value for key in etcd_entry
 | 
					                    key.key.split("/")[-1]: key.value
 | 
				
			||||||
 | 
					                    for key in etcd_entry
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return {"keys": keys}
 | 
					                return {"keys": keys}
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # {user_prefix}/{realm}/{name}/key/{key_name}
 | 
					                # {user_prefix}/{realm}/{name}/key/{key_name}
 | 
				
			||||||
                etcd_key = join_path(
 | 
					                etcd_key = join_path(
 | 
				
			||||||
                    settings['etcd']['user_prefix'],
 | 
					                    settings["etcd"]["user_prefix"],
 | 
				
			||||||
                    data["realm"],
 | 
					                    data["realm"],
 | 
				
			||||||
                    data["name"],
 | 
					                    data["name"],
 | 
				
			||||||
                    "key",
 | 
					                    "key",
 | 
				
			||||||
                    data["key_name"],
 | 
					                    data["key_name"],
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                etcd_entry = shared.etcd_client.get(etcd_key, value_in_json=True)
 | 
					                etcd_entry = shared.etcd_client.get(
 | 
				
			||||||
 | 
					                    etcd_key, value_in_json=True
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if etcd_entry:
 | 
					                if etcd_entry:
 | 
				
			||||||
                    return {
 | 
					                    return {
 | 
				
			||||||
                        "keys": {
 | 
					                        "keys": {
 | 
				
			||||||
                            etcd_entry.key.split("/")[-1]: etcd_entry.value
 | 
					                            etcd_entry.key.split("/")[
 | 
				
			||||||
 | 
					                                -1
 | 
				
			||||||
 | 
					                            ]: etcd_entry.value
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
| 
						 | 
					@ -379,13 +412,15 @@ class AddSSHKey(Resource):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # {user_prefix}/{realm}/{name}/key/{key_name}
 | 
					            # {user_prefix}/{realm}/{name}/key/{key_name}
 | 
				
			||||||
            etcd_key = join_path(
 | 
					            etcd_key = join_path(
 | 
				
			||||||
                settings['etcd']['user_prefix'],
 | 
					                settings["etcd"]["user_prefix"],
 | 
				
			||||||
                data["realm"],
 | 
					                data["realm"],
 | 
				
			||||||
                data["name"],
 | 
					                data["name"],
 | 
				
			||||||
                "key",
 | 
					                "key",
 | 
				
			||||||
                data["key_name"],
 | 
					                data["key_name"],
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            etcd_entry = shared.etcd_client.get(etcd_key, value_in_json=True)
 | 
					            etcd_entry = shared.etcd_client.get(
 | 
				
			||||||
 | 
					                etcd_key, value_in_json=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            if etcd_entry:
 | 
					            if etcd_entry:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    "message": "Key with name '{}' already exists".format(
 | 
					                    "message": "Key with name '{}' already exists".format(
 | 
				
			||||||
| 
						 | 
					@ -394,7 +429,9 @@ class AddSSHKey(Resource):
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                # Key Not Found. It implies user' haven't added any key yet.
 | 
					                # Key Not Found. It implies user' haven't added any key yet.
 | 
				
			||||||
                shared.etcd_client.put(etcd_key, data["key"], value_in_json=True)
 | 
					                shared.etcd_client.put(
 | 
				
			||||||
 | 
					                    etcd_key, data["key"], value_in_json=True
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                return {"message": "Key added successfully"}
 | 
					                return {"message": "Key added successfully"}
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return validator.get_errors(), 400
 | 
					            return validator.get_errors(), 400
 | 
				
			||||||
| 
						 | 
					@ -409,13 +446,15 @@ class RemoveSSHKey(Resource):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # {user_prefix}/{realm}/{name}/key/{key_name}
 | 
					            # {user_prefix}/{realm}/{name}/key/{key_name}
 | 
				
			||||||
            etcd_key = join_path(
 | 
					            etcd_key = join_path(
 | 
				
			||||||
                settings['etcd']['user_prefix'],
 | 
					                settings["etcd"]["user_prefix"],
 | 
				
			||||||
                data["realm"],
 | 
					                data["realm"],
 | 
				
			||||||
                data["name"],
 | 
					                data["name"],
 | 
				
			||||||
                "key",
 | 
					                "key",
 | 
				
			||||||
                data["key_name"],
 | 
					                data["key_name"],
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            etcd_entry = shared.etcd_client.get(etcd_key, value_in_json=True)
 | 
					            etcd_entry = shared.etcd_client.get(
 | 
				
			||||||
 | 
					                etcd_key, value_in_json=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            if etcd_entry:
 | 
					            if etcd_entry:
 | 
				
			||||||
                shared.etcd_client.client.delete(etcd_key)
 | 
					                shared.etcd_client.client.delete(etcd_key)
 | 
				
			||||||
                return {"message": "Key successfully removed."}
 | 
					                return {"message": "Key successfully removed."}
 | 
				
			||||||
| 
						 | 
					@ -446,15 +485,17 @@ class CreateNetwork(Resource):
 | 
				
			||||||
            if validator.user.value:
 | 
					            if validator.user.value:
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    nb = pynetbox.api(
 | 
					                    nb = pynetbox.api(
 | 
				
			||||||
                        url=settings['netbox']['url'],
 | 
					                        url=settings["netbox"]["url"],
 | 
				
			||||||
                        token=settings['netbox']['token'],
 | 
					                        token=settings["netbox"]["token"],
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    nb_prefix = nb.ipam.prefixes.get(
 | 
					                    nb_prefix = nb.ipam.prefixes.get(
 | 
				
			||||||
                        prefix=settings['network']['prefix']
 | 
					                        prefix=settings["network"]["prefix"]
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    prefix = nb_prefix.available_prefixes.create(
 | 
					                    prefix = nb_prefix.available_prefixes.create(
 | 
				
			||||||
                        data={
 | 
					                        data={
 | 
				
			||||||
                            "prefix_length": int(settings['network']['prefix_length']),
 | 
					                            "prefix_length": int(
 | 
				
			||||||
 | 
					                                settings["network"]["prefix_length"]
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
                            "description": '{}\'s network "{}"'.format(
 | 
					                            "description": '{}\'s network "{}"'.format(
 | 
				
			||||||
                                data["name"], data["network_name"]
 | 
					                                data["name"], data["network_name"]
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
| 
						 | 
					@ -463,18 +504,22 @@ class CreateNetwork(Resource):
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                except Exception as err:
 | 
					                except Exception as err:
 | 
				
			||||||
                    app.logger.error(err)
 | 
					                    app.logger.error(err)
 | 
				
			||||||
                    return {"message": "Error occured while creating network."}
 | 
					                    return {
 | 
				
			||||||
 | 
					                        "message": "Error occured while creating network."
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    network_entry["ipv6"] = prefix["prefix"]
 | 
					                    network_entry["ipv6"] = prefix["prefix"]
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                network_entry["ipv6"] = "fd00::/64"
 | 
					                network_entry["ipv6"] = "fd00::/64"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            network_key = join_path(
 | 
					            network_key = join_path(
 | 
				
			||||||
                settings['etcd']['network_prefix'],
 | 
					                settings["etcd"]["network_prefix"],
 | 
				
			||||||
                data['name'],
 | 
					                data["name"],
 | 
				
			||||||
                data['network_name'],
 | 
					                data["network_name"],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            shared.etcd_client.put(
 | 
				
			||||||
 | 
					                network_key, network_entry, value_in_json=True
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            shared.etcd_client.put(network_key, network_entry, value_in_json=True)
 | 
					 | 
				
			||||||
            return {"message": "Network successfully added."}
 | 
					            return {"message": "Network successfully added."}
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return validator.get_errors(), 400
 | 
					            return validator.get_errors(), 400
 | 
				
			||||||
| 
						 | 
					@ -488,9 +533,11 @@ class ListUserNetwork(Resource):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if validator.is_valid():
 | 
					        if validator.is_valid():
 | 
				
			||||||
            prefix = join_path(
 | 
					            prefix = join_path(
 | 
				
			||||||
                settings['etcd']['network_prefix'], data["name"]
 | 
					                settings["etcd"]["network_prefix"], data["name"]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            networks = shared.etcd_client.get_prefix(
 | 
				
			||||||
 | 
					                prefix, value_in_json=True
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            networks = shared.etcd_client.get_prefix(prefix, value_in_json=True)
 | 
					 | 
				
			||||||
            user_networks = []
 | 
					            user_networks = []
 | 
				
			||||||
            for net in networks:
 | 
					            for net in networks:
 | 
				
			||||||
                net.value["name"] = net.key.split("/")[-1]
 | 
					                net.value["name"] = net.key.split("/")[-1]
 | 
				
			||||||
| 
						 | 
					@ -524,7 +571,11 @@ api.add_resource(CreateNetwork, "/network/create")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
    image_stores = list(shared.etcd_client.get_prefix(settings['etcd']['image_store_prefix'], value_in_json=True))
 | 
					    image_stores = list(
 | 
				
			||||||
 | 
					        shared.etcd_client.get_prefix(
 | 
				
			||||||
 | 
					            settings["etcd"]["image_store_prefix"], value_in_json=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    if len(image_stores) == 0:
 | 
					    if len(image_stores) == 0:
 | 
				
			||||||
        data = {
 | 
					        data = {
 | 
				
			||||||
            "is_public": True,
 | 
					            "is_public": True,
 | 
				
			||||||
| 
						 | 
					@ -534,7 +585,12 @@ def main():
 | 
				
			||||||
            "attributes": {"list": [], "key": [], "pool": "images"},
 | 
					            "attributes": {"list": [], "key": [], "pool": "images"},
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        shared.etcd_client.put(join_path(settings['etcd']['image_store_prefix'], uuid4().hex), json.dumps(data))
 | 
					        shared.etcd_client.put(
 | 
				
			||||||
 | 
					            join_path(
 | 
				
			||||||
 | 
					                settings["etcd"]["image_store_prefix"], uuid4().hex
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            json.dumps(data),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app.run(host="::", debug=True)
 | 
					    app.run(host="::", debug=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,7 +80,12 @@ class OTPSchema(BaseSchema):
 | 
				
			||||||
        super().__init__(data=data, fields=_fields)
 | 
					        super().__init__(data=data, fields=_fields)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validation(self):
 | 
					    def validation(self):
 | 
				
			||||||
        if check_otp(self.name.value, self.realm.value, self.token.value) != 200:
 | 
					        if (
 | 
				
			||||||
 | 
					            check_otp(
 | 
				
			||||||
 | 
					                self.name.value, self.realm.value, self.token.value
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            != 200
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
            self.add_error("Wrong Credentials")
 | 
					            self.add_error("Wrong Credentials")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -92,7 +97,9 @@ class CreateImageSchema(BaseSchema):
 | 
				
			||||||
        # Fields
 | 
					        # Fields
 | 
				
			||||||
        self.uuid = Field("uuid", str, data.get("uuid", KeyError))
 | 
					        self.uuid = Field("uuid", str, data.get("uuid", KeyError))
 | 
				
			||||||
        self.name = Field("name", str, data.get("name", KeyError))
 | 
					        self.name = Field("name", str, data.get("name", KeyError))
 | 
				
			||||||
        self.image_store = Field("image_store", str, data.get("image_store", KeyError))
 | 
					        self.image_store = Field(
 | 
				
			||||||
 | 
					            "image_store", str, data.get("image_store", KeyError)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Validations
 | 
					        # Validations
 | 
				
			||||||
        self.uuid.validation = self.file_uuid_validation
 | 
					        self.uuid.validation = self.file_uuid_validation
 | 
				
			||||||
| 
						 | 
					@ -103,34 +110,52 @@ class CreateImageSchema(BaseSchema):
 | 
				
			||||||
        super().__init__(data, fields)
 | 
					        super().__init__(data, fields)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def file_uuid_validation(self):
 | 
					    def file_uuid_validation(self):
 | 
				
			||||||
        file_entry = shared.etcd_client.get(os.path.join(settings['etcd']['file_prefix'], self.uuid.value))
 | 
					        file_entry = shared.etcd_client.get(
 | 
				
			||||||
 | 
					            os.path.join(
 | 
				
			||||||
 | 
					                settings["etcd"]["file_prefix"], self.uuid.value
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        if file_entry is None:
 | 
					        if file_entry is None:
 | 
				
			||||||
            self.add_error(
 | 
					            self.add_error(
 | 
				
			||||||
                "Image File with uuid '{}' Not Found".format(self.uuid.value)
 | 
					                "Image File with uuid '{}' Not Found".format(
 | 
				
			||||||
 | 
					                    self.uuid.value
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def image_store_name_validation(self):
 | 
					    def image_store_name_validation(self):
 | 
				
			||||||
        image_stores = list(shared.etcd_client.get_prefix(settings['etcd']['image_store_prefix']))
 | 
					        image_stores = list(
 | 
				
			||||||
 | 
					            shared.etcd_client.get_prefix(
 | 
				
			||||||
 | 
					                settings["etcd"]["image_store_prefix"]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        image_store = next(
 | 
					        image_store = next(
 | 
				
			||||||
            filter(
 | 
					            filter(
 | 
				
			||||||
                lambda s: json.loads(s.value)["name"] == self.image_store.value,
 | 
					                lambda s: json.loads(s.value)["name"]
 | 
				
			||||||
 | 
					                == self.image_store.value,
 | 
				
			||||||
                image_stores,
 | 
					                image_stores,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            None,
 | 
					            None,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        if not image_store:
 | 
					        if not image_store:
 | 
				
			||||||
            self.add_error("Store '{}' does not exists".format(self.image_store.value))
 | 
					            self.add_error(
 | 
				
			||||||
 | 
					                "Store '{}' does not exists".format(
 | 
				
			||||||
 | 
					                    self.image_store.value
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Host Operations
 | 
					# Host Operations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CreateHostSchema(OTPSchema):
 | 
					class CreateHostSchema(OTPSchema):
 | 
				
			||||||
    def __init__(self, data):
 | 
					    def __init__(self, data):
 | 
				
			||||||
        self.parsed_specs = {}
 | 
					        self.parsed_specs = {}
 | 
				
			||||||
        # Fields
 | 
					        # Fields
 | 
				
			||||||
        self.specs = Field("specs", dict, data.get("specs", KeyError))
 | 
					        self.specs = Field("specs", dict, data.get("specs", KeyError))
 | 
				
			||||||
        self.hostname = Field("hostname", str, data.get("hostname", KeyError))
 | 
					        self.hostname = Field(
 | 
				
			||||||
 | 
					            "hostname", str, data.get("hostname", KeyError)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Validation
 | 
					        # Validation
 | 
				
			||||||
        self.specs.validation = self.specs_validation
 | 
					        self.specs.validation = self.specs_validation
 | 
				
			||||||
| 
						 | 
					@ -142,22 +167,28 @@ class CreateHostSchema(OTPSchema):
 | 
				
			||||||
    def specs_validation(self):
 | 
					    def specs_validation(self):
 | 
				
			||||||
        ALLOWED_BASE = 10
 | 
					        ALLOWED_BASE = 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _cpu = self.specs.value.get('cpu', KeyError)
 | 
					        _cpu = self.specs.value.get("cpu", KeyError)
 | 
				
			||||||
        _ram = self.specs.value.get('ram', KeyError)
 | 
					        _ram = self.specs.value.get("ram", KeyError)
 | 
				
			||||||
        _os_ssd = self.specs.value.get('os-ssd', KeyError)
 | 
					        _os_ssd = self.specs.value.get("os-ssd", KeyError)
 | 
				
			||||||
        _hdd = self.specs.value.get('hdd', KeyError)
 | 
					        _hdd = self.specs.value.get("hdd", KeyError)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if KeyError in [_cpu, _ram, _os_ssd, _hdd]:
 | 
					        if KeyError in [_cpu, _ram, _os_ssd, _hdd]:
 | 
				
			||||||
            self.add_error("You must specify CPU, RAM and OS-SSD in your specs")
 | 
					            self.add_error(
 | 
				
			||||||
 | 
					                "You must specify CPU, RAM and OS-SSD in your specs"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            parsed_ram = bitmath.parse_string_unsafe(_ram)
 | 
					            parsed_ram = bitmath.parse_string_unsafe(_ram)
 | 
				
			||||||
            parsed_os_ssd = bitmath.parse_string_unsafe(_os_ssd)
 | 
					            parsed_os_ssd = bitmath.parse_string_unsafe(_os_ssd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if parsed_ram.base != ALLOWED_BASE:
 | 
					            if parsed_ram.base != ALLOWED_BASE:
 | 
				
			||||||
                self.add_error("Your specified RAM is not in correct units")
 | 
					                self.add_error(
 | 
				
			||||||
 | 
					                    "Your specified RAM is not in correct units"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            if parsed_os_ssd.base != ALLOWED_BASE:
 | 
					            if parsed_os_ssd.base != ALLOWED_BASE:
 | 
				
			||||||
                self.add_error("Your specified OS-SSD is not in correct units")
 | 
					                self.add_error(
 | 
				
			||||||
 | 
					                    "Your specified OS-SSD is not in correct units"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if _cpu < 1:
 | 
					            if _cpu < 1:
 | 
				
			||||||
                self.add_error("CPU must be atleast 1")
 | 
					                self.add_error("CPU must be atleast 1")
 | 
				
			||||||
| 
						 | 
					@ -172,7 +203,9 @@ class CreateHostSchema(OTPSchema):
 | 
				
			||||||
            for hdd in _hdd:
 | 
					            for hdd in _hdd:
 | 
				
			||||||
                _parsed_hdd = bitmath.parse_string_unsafe(hdd)
 | 
					                _parsed_hdd = bitmath.parse_string_unsafe(hdd)
 | 
				
			||||||
                if _parsed_hdd.base != ALLOWED_BASE:
 | 
					                if _parsed_hdd.base != ALLOWED_BASE:
 | 
				
			||||||
                    self.add_error("Your specified HDD is not in correct units")
 | 
					                    self.add_error(
 | 
				
			||||||
 | 
					                        "Your specified HDD is not in correct units"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    break
 | 
					                    break
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    parsed_hdd.append(str(_parsed_hdd))
 | 
					                    parsed_hdd.append(str(_parsed_hdd))
 | 
				
			||||||
| 
						 | 
					@ -183,15 +216,17 @@ class CreateHostSchema(OTPSchema):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            if self.get_errors():
 | 
					            if self.get_errors():
 | 
				
			||||||
                self.specs = {
 | 
					                self.specs = {
 | 
				
			||||||
                    'cpu': _cpu,
 | 
					                    "cpu": _cpu,
 | 
				
			||||||
                    'ram': str(parsed_ram),
 | 
					                    "ram": str(parsed_ram),
 | 
				
			||||||
                    'os-ssd': str(parsed_os_ssd),
 | 
					                    "os-ssd": str(parsed_os_ssd),
 | 
				
			||||||
                    'hdd': parsed_hdd
 | 
					                    "hdd": parsed_hdd,
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validation(self):
 | 
					    def validation(self):
 | 
				
			||||||
        if self.realm.value != "ungleich-admin":
 | 
					        if self.realm.value != "ungleich-admin":
 | 
				
			||||||
            self.add_error("Invalid Credentials/Insufficient Permission")
 | 
					            self.add_error(
 | 
				
			||||||
 | 
					                "Invalid Credentials/Insufficient Permission"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# VM Operations
 | 
					# VM Operations
 | 
				
			||||||
| 
						 | 
					@ -203,9 +238,13 @@ class CreateVMSchema(OTPSchema):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Fields
 | 
					        # Fields
 | 
				
			||||||
        self.specs = Field("specs", dict, data.get("specs", KeyError))
 | 
					        self.specs = Field("specs", dict, data.get("specs", KeyError))
 | 
				
			||||||
        self.vm_name = Field("vm_name", str, data.get("vm_name", KeyError))
 | 
					        self.vm_name = Field(
 | 
				
			||||||
 | 
					            "vm_name", str, data.get("vm_name", KeyError)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        self.image = Field("image", str, data.get("image", KeyError))
 | 
					        self.image = Field("image", str, data.get("image", KeyError))
 | 
				
			||||||
        self.network = Field("network", list, data.get("network", KeyError))
 | 
					        self.network = Field(
 | 
				
			||||||
 | 
					            "network", list, data.get("network", KeyError)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Validation
 | 
					        # Validation
 | 
				
			||||||
        self.image.validation = self.image_validation
 | 
					        self.image.validation = self.image_validation
 | 
				
			||||||
| 
						 | 
					@ -219,17 +258,25 @@ class CreateVMSchema(OTPSchema):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def image_validation(self):
 | 
					    def image_validation(self):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            image_uuid = helper.resolve_image_name(self.image.value, shared.etcd_client)
 | 
					            image_uuid = helper.resolve_image_name(
 | 
				
			||||||
 | 
					                self.image.value, shared.etcd_client
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            logger.exception("Cannot resolve image name = %s", self.image.value)
 | 
					            logger.exception(
 | 
				
			||||||
 | 
					                "Cannot resolve image name = %s", self.image.value
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            self.add_error(str(e))
 | 
					            self.add_error(str(e))
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.image_uuid = image_uuid
 | 
					            self.image_uuid = image_uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def vm_name_validation(self):
 | 
					    def vm_name_validation(self):
 | 
				
			||||||
        if resolve_vm_name(name=self.vm_name.value, owner=self.name.value):
 | 
					        if resolve_vm_name(
 | 
				
			||||||
 | 
					            name=self.vm_name.value, owner=self.name.value
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
            self.add_error(
 | 
					            self.add_error(
 | 
				
			||||||
                'VM with same name "{}" already exists'.format(self.vm_name.value)
 | 
					                'VM with same name "{}" already exists'.format(
 | 
				
			||||||
 | 
					                    self.vm_name.value
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def network_validation(self):
 | 
					    def network_validation(self):
 | 
				
			||||||
| 
						 | 
					@ -237,32 +284,46 @@ class CreateVMSchema(OTPSchema):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if _network:
 | 
					        if _network:
 | 
				
			||||||
            for net in _network:
 | 
					            for net in _network:
 | 
				
			||||||
                network = shared.etcd_client.get(os.path.join(settings['etcd']['network_prefix'],
 | 
					                network = shared.etcd_client.get(
 | 
				
			||||||
 | 
					                    os.path.join(
 | 
				
			||||||
 | 
					                        settings["etcd"]["network_prefix"],
 | 
				
			||||||
                        self.name.value,
 | 
					                        self.name.value,
 | 
				
			||||||
                                                       net), value_in_json=True)
 | 
					                        net,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    value_in_json=True,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                if not network:
 | 
					                if not network:
 | 
				
			||||||
                    self.add_error("Network with name {} does not exists" \
 | 
					                    self.add_error(
 | 
				
			||||||
                                   .format(net))
 | 
					                        "Network with name {} does not exists".format(
 | 
				
			||||||
 | 
					                            net
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def specs_validation(self):
 | 
					    def specs_validation(self):
 | 
				
			||||||
        ALLOWED_BASE = 10
 | 
					        ALLOWED_BASE = 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _cpu = self.specs.value.get('cpu', KeyError)
 | 
					        _cpu = self.specs.value.get("cpu", KeyError)
 | 
				
			||||||
        _ram = self.specs.value.get('ram', KeyError)
 | 
					        _ram = self.specs.value.get("ram", KeyError)
 | 
				
			||||||
        _os_ssd = self.specs.value.get('os-ssd', KeyError)
 | 
					        _os_ssd = self.specs.value.get("os-ssd", KeyError)
 | 
				
			||||||
        _hdd = self.specs.value.get('hdd', KeyError)
 | 
					        _hdd = self.specs.value.get("hdd", KeyError)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if KeyError in [_cpu, _ram, _os_ssd, _hdd]:
 | 
					        if KeyError in [_cpu, _ram, _os_ssd, _hdd]:
 | 
				
			||||||
            self.add_error("You must specify CPU, RAM and OS-SSD in your specs")
 | 
					            self.add_error(
 | 
				
			||||||
 | 
					                "You must specify CPU, RAM and OS-SSD in your specs"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            parsed_ram = bitmath.parse_string_unsafe(_ram)
 | 
					            parsed_ram = bitmath.parse_string_unsafe(_ram)
 | 
				
			||||||
            parsed_os_ssd = bitmath.parse_string_unsafe(_os_ssd)
 | 
					            parsed_os_ssd = bitmath.parse_string_unsafe(_os_ssd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if parsed_ram.base != ALLOWED_BASE:
 | 
					            if parsed_ram.base != ALLOWED_BASE:
 | 
				
			||||||
                self.add_error("Your specified RAM is not in correct units")
 | 
					                self.add_error(
 | 
				
			||||||
 | 
					                    "Your specified RAM is not in correct units"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            if parsed_os_ssd.base != ALLOWED_BASE:
 | 
					            if parsed_os_ssd.base != ALLOWED_BASE:
 | 
				
			||||||
                self.add_error("Your specified OS-SSD is not in correct units")
 | 
					                self.add_error(
 | 
				
			||||||
 | 
					                    "Your specified OS-SSD is not in correct units"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if _cpu < 1:
 | 
					            if _cpu < 1:
 | 
				
			||||||
                self.add_error("CPU must be atleast 1")
 | 
					                self.add_error("CPU must be atleast 1")
 | 
				
			||||||
| 
						 | 
					@ -277,7 +338,9 @@ class CreateVMSchema(OTPSchema):
 | 
				
			||||||
            for hdd in _hdd:
 | 
					            for hdd in _hdd:
 | 
				
			||||||
                _parsed_hdd = bitmath.parse_string_unsafe(hdd)
 | 
					                _parsed_hdd = bitmath.parse_string_unsafe(hdd)
 | 
				
			||||||
                if _parsed_hdd.base != ALLOWED_BASE:
 | 
					                if _parsed_hdd.base != ALLOWED_BASE:
 | 
				
			||||||
                    self.add_error("Your specified HDD is not in correct units")
 | 
					                    self.add_error(
 | 
				
			||||||
 | 
					                        "Your specified HDD is not in correct units"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    break
 | 
					                    break
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    parsed_hdd.append(str(_parsed_hdd))
 | 
					                    parsed_hdd.append(str(_parsed_hdd))
 | 
				
			||||||
| 
						 | 
					@ -288,10 +351,10 @@ class CreateVMSchema(OTPSchema):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            if self.get_errors():
 | 
					            if self.get_errors():
 | 
				
			||||||
                self.specs = {
 | 
					                self.specs = {
 | 
				
			||||||
                    'cpu': _cpu,
 | 
					                    "cpu": _cpu,
 | 
				
			||||||
                    'ram': str(parsed_ram),
 | 
					                    "ram": str(parsed_ram),
 | 
				
			||||||
                    'os-ssd': str(parsed_os_ssd),
 | 
					                    "os-ssd": str(parsed_os_ssd),
 | 
				
			||||||
                    'hdd': parsed_hdd
 | 
					                    "hdd": parsed_hdd,
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -300,7 +363,10 @@ class VMStatusSchema(OTPSchema):
 | 
				
			||||||
        data["uuid"] = (
 | 
					        data["uuid"] = (
 | 
				
			||||||
            resolve_vm_name(
 | 
					            resolve_vm_name(
 | 
				
			||||||
                name=data.get("vm_name", None),
 | 
					                name=data.get("vm_name", None),
 | 
				
			||||||
                    owner=(data.get("in_support_of", None) or data.get("name", None)),
 | 
					                owner=(
 | 
				
			||||||
 | 
					                    data.get("in_support_of", None)
 | 
				
			||||||
 | 
					                    or data.get("name", None)
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            or KeyError
 | 
					            or KeyError
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -313,7 +379,8 @@ class VMStatusSchema(OTPSchema):
 | 
				
			||||||
    def validation(self):
 | 
					    def validation(self):
 | 
				
			||||||
        vm = shared.vm_pool.get(self.uuid.value)
 | 
					        vm = shared.vm_pool.get(self.uuid.value)
 | 
				
			||||||
        if not (
 | 
					        if not (
 | 
				
			||||||
                vm.value["owner"] == self.name.value or self.realm.value == "ungleich-admin"
 | 
					            vm.value["owner"] == self.name.value
 | 
				
			||||||
 | 
					            or self.realm.value == "ungleich-admin"
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            self.add_error("Invalid User")
 | 
					            self.add_error("Invalid User")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -323,7 +390,10 @@ class VmActionSchema(OTPSchema):
 | 
				
			||||||
        data["uuid"] = (
 | 
					        data["uuid"] = (
 | 
				
			||||||
            resolve_vm_name(
 | 
					            resolve_vm_name(
 | 
				
			||||||
                name=data.get("vm_name", None),
 | 
					                name=data.get("vm_name", None),
 | 
				
			||||||
                    owner=(data.get("in_support_of", None) or data.get("name", None)),
 | 
					                owner=(
 | 
				
			||||||
 | 
					                    data.get("in_support_of", None)
 | 
				
			||||||
 | 
					                    or data.get("name", None)
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            or KeyError
 | 
					            or KeyError
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -340,13 +410,16 @@ class VmActionSchema(OTPSchema):
 | 
				
			||||||
        allowed_actions = ["start", "stop", "delete"]
 | 
					        allowed_actions = ["start", "stop", "delete"]
 | 
				
			||||||
        if self.action.value not in allowed_actions:
 | 
					        if self.action.value not in allowed_actions:
 | 
				
			||||||
            self.add_error(
 | 
					            self.add_error(
 | 
				
			||||||
                "Invalid Action. Allowed Actions are {}".format(allowed_actions)
 | 
					                "Invalid Action. Allowed Actions are {}".format(
 | 
				
			||||||
 | 
					                    allowed_actions
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validation(self):
 | 
					    def validation(self):
 | 
				
			||||||
        vm = shared.vm_pool.get(self.uuid.value)
 | 
					        vm = shared.vm_pool.get(self.uuid.value)
 | 
				
			||||||
        if not (
 | 
					        if not (
 | 
				
			||||||
                vm.value["owner"] == self.name.value or self.realm.value == "ungleich-admin"
 | 
					            vm.value["owner"] == self.name.value
 | 
				
			||||||
 | 
					            or self.realm.value == "ungleich-admin"
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            self.add_error("Invalid User")
 | 
					            self.add_error("Invalid User")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -369,13 +442,18 @@ class VmMigrationSchema(OTPSchema):
 | 
				
			||||||
        data["uuid"] = (
 | 
					        data["uuid"] = (
 | 
				
			||||||
            resolve_vm_name(
 | 
					            resolve_vm_name(
 | 
				
			||||||
                name=data.get("vm_name", None),
 | 
					                name=data.get("vm_name", None),
 | 
				
			||||||
                    owner=(data.get("in_support_of", None) or data.get("name", None)),
 | 
					                owner=(
 | 
				
			||||||
 | 
					                    data.get("in_support_of", None)
 | 
				
			||||||
 | 
					                    or data.get("name", None)
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            or KeyError
 | 
					            or KeyError
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.uuid = VmUUIDField(data)
 | 
					        self.uuid = VmUUIDField(data)
 | 
				
			||||||
        self.destination = Field("destination", str, data.get("destination", KeyError))
 | 
					        self.destination = Field(
 | 
				
			||||||
 | 
					            "destination", str, data.get("destination", KeyError)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.destination.validation = self.destination_validation
 | 
					        self.destination.validation = self.destination_validation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -384,9 +462,18 @@ class VmMigrationSchema(OTPSchema):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def destination_validation(self):
 | 
					    def destination_validation(self):
 | 
				
			||||||
        hostname = self.destination.value
 | 
					        hostname = self.destination.value
 | 
				
			||||||
        host = next(filter(lambda h: h.hostname == hostname, shared.host_pool.hosts), None)
 | 
					        host = next(
 | 
				
			||||||
 | 
					            filter(
 | 
				
			||||||
 | 
					                lambda h: h.hostname == hostname, shared.host_pool.hosts
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        if not host:
 | 
					        if not host:
 | 
				
			||||||
            self.add_error("No Such Host ({}) exists".format(self.destination.value))
 | 
					            self.add_error(
 | 
				
			||||||
 | 
					                "No Such Host ({}) exists".format(
 | 
				
			||||||
 | 
					                    self.destination.value
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        elif host.status != HostStatus.alive:
 | 
					        elif host.status != HostStatus.alive:
 | 
				
			||||||
            self.add_error("Destination Host is dead")
 | 
					            self.add_error("Destination Host is dead")
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -395,20 +482,27 @@ class VmMigrationSchema(OTPSchema):
 | 
				
			||||||
    def validation(self):
 | 
					    def validation(self):
 | 
				
			||||||
        vm = shared.vm_pool.get(self.uuid.value)
 | 
					        vm = shared.vm_pool.get(self.uuid.value)
 | 
				
			||||||
        if not (
 | 
					        if not (
 | 
				
			||||||
                vm.value["owner"] == self.name.value or self.realm.value == "ungleich-admin"
 | 
					            vm.value["owner"] == self.name.value
 | 
				
			||||||
 | 
					            or self.realm.value == "ungleich-admin"
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            self.add_error("Invalid User")
 | 
					            self.add_error("Invalid User")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if vm.status != VMStatus.running:
 | 
					        if vm.status != VMStatus.running:
 | 
				
			||||||
            self.add_error("Can't migrate non-running VM")
 | 
					            self.add_error("Can't migrate non-running VM")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if vm.hostname == os.path.join(settings['etcd']['host_prefix'], self.destination.value):
 | 
					        if vm.hostname == os.path.join(
 | 
				
			||||||
            self.add_error("Destination host couldn't be same as Source Host")
 | 
					            settings["etcd"]["host_prefix"], self.destination.value
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            self.add_error(
 | 
				
			||||||
 | 
					                "Destination host couldn't be same as Source Host"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AddSSHSchema(OTPSchema):
 | 
					class AddSSHSchema(OTPSchema):
 | 
				
			||||||
    def __init__(self, data):
 | 
					    def __init__(self, data):
 | 
				
			||||||
        self.key_name = Field("key_name", str, data.get("key_name", KeyError))
 | 
					        self.key_name = Field(
 | 
				
			||||||
 | 
					            "key_name", str, data.get("key_name", KeyError)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        self.key = Field("key", str, data.get("key_name", KeyError))
 | 
					        self.key = Field("key", str, data.get("key_name", KeyError))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fields = [self.key_name, self.key]
 | 
					        fields = [self.key_name, self.key]
 | 
				
			||||||
| 
						 | 
					@ -417,7 +511,9 @@ class AddSSHSchema(OTPSchema):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RemoveSSHSchema(OTPSchema):
 | 
					class RemoveSSHSchema(OTPSchema):
 | 
				
			||||||
    def __init__(self, data):
 | 
					    def __init__(self, data):
 | 
				
			||||||
        self.key_name = Field("key_name", str, data.get("key_name", KeyError))
 | 
					        self.key_name = Field(
 | 
				
			||||||
 | 
					            "key_name", str, data.get("key_name", KeyError)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fields = [self.key_name]
 | 
					        fields = [self.key_name]
 | 
				
			||||||
        super().__init__(data=data, fields=fields)
 | 
					        super().__init__(data=data, fields=fields)
 | 
				
			||||||
| 
						 | 
					@ -425,7 +521,9 @@ class RemoveSSHSchema(OTPSchema):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GetSSHSchema(OTPSchema):
 | 
					class GetSSHSchema(OTPSchema):
 | 
				
			||||||
    def __init__(self, data):
 | 
					    def __init__(self, data):
 | 
				
			||||||
        self.key_name = Field("key_name", str, data.get("key_name", None))
 | 
					        self.key_name = Field(
 | 
				
			||||||
 | 
					            "key_name", str, data.get("key_name", None)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fields = [self.key_name]
 | 
					        fields = [self.key_name]
 | 
				
			||||||
        super().__init__(data=data, fields=fields)
 | 
					        super().__init__(data=data, fields=fields)
 | 
				
			||||||
| 
						 | 
					@ -433,7 +531,9 @@ class GetSSHSchema(OTPSchema):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CreateNetwork(OTPSchema):
 | 
					class CreateNetwork(OTPSchema):
 | 
				
			||||||
    def __init__(self, data):
 | 
					    def __init__(self, data):
 | 
				
			||||||
        self.network_name = Field("network_name", str, data.get("network_name", KeyError))
 | 
					        self.network_name = Field(
 | 
				
			||||||
 | 
					            "network_name", str, data.get("network_name", KeyError)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        self.type = Field("type", str, data.get("type", KeyError))
 | 
					        self.type = Field("type", str, data.get("type", KeyError))
 | 
				
			||||||
        self.user = Field("user", bool, bool(data.get("user", False)))
 | 
					        self.user = Field("user", bool, bool(data.get("user", False)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -444,15 +544,26 @@ class CreateNetwork(OTPSchema):
 | 
				
			||||||
        super().__init__(data, fields=fields)
 | 
					        super().__init__(data, fields=fields)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def network_name_validation(self):
 | 
					    def network_name_validation(self):
 | 
				
			||||||
        network = shared.etcd_client.get(os.path.join(settings['etcd']['network_prefix'],
 | 
					        network = shared.etcd_client.get(
 | 
				
			||||||
 | 
					            os.path.join(
 | 
				
			||||||
 | 
					                settings["etcd"]["network_prefix"],
 | 
				
			||||||
                self.name.value,
 | 
					                self.name.value,
 | 
				
			||||||
                                               self.network_name.value),
 | 
					                self.network_name.value,
 | 
				
			||||||
                                  value_in_json=True)
 | 
					            ),
 | 
				
			||||||
 | 
					            value_in_json=True,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        if network:
 | 
					        if network:
 | 
				
			||||||
            self.add_error("Network with name {} already exists" \
 | 
					            self.add_error(
 | 
				
			||||||
                           .format(self.network_name.value))
 | 
					                "Network with name {} already exists".format(
 | 
				
			||||||
 | 
					                    self.network_name.value
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def network_type_validation(self):
 | 
					    def network_type_validation(self):
 | 
				
			||||||
        supported_network_types = ["vxlan"]
 | 
					        supported_network_types = ["vxlan"]
 | 
				
			||||||
        if self.type.value not in supported_network_types:
 | 
					        if self.type.value not in supported_network_types:
 | 
				
			||||||
            self.add_error("Unsupported Network Type. Supported network types are {}".format(supported_network_types))
 | 
					            self.add_error(
 | 
				
			||||||
 | 
					                "Unsupported Network Type. Supported network types are {}".format(
 | 
				
			||||||
 | 
					                    supported_network_types
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ from functools import wraps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import logger
 | 
					from . import logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PseudoEtcdMeta = namedtuple('PseudoEtcdMeta', ['key'])
 | 
					PseudoEtcdMeta = namedtuple("PseudoEtcdMeta", ["key"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EtcdEntry:
 | 
					class EtcdEntry:
 | 
				
			||||||
| 
						 | 
					@ -16,8 +16,8 @@ class EtcdEntry:
 | 
				
			||||||
    # value: str
 | 
					    # value: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, meta, value, value_in_json=False):
 | 
					    def __init__(self, meta, value, value_in_json=False):
 | 
				
			||||||
        self.key = meta.key.decode('utf-8')
 | 
					        self.key = meta.key.decode("utf-8")
 | 
				
			||||||
        self.value = value.decode('utf-8')
 | 
					        self.value = value.decode("utf-8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if value_in_json:
 | 
					        if value_in_json:
 | 
				
			||||||
            self.value = json.loads(self.value)
 | 
					            self.value = json.loads(self.value)
 | 
				
			||||||
| 
						 | 
					@ -29,11 +29,18 @@ def readable_errors(func):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            return func(*args, **kwargs)
 | 
					            return func(*args, **kwargs)
 | 
				
			||||||
        except etcd3.exceptions.ConnectionFailedError as err:
 | 
					        except etcd3.exceptions.ConnectionFailedError as err:
 | 
				
			||||||
            raise etcd3.exceptions.ConnectionFailedError('etcd connection failed.') from err
 | 
					            raise etcd3.exceptions.ConnectionFailedError(
 | 
				
			||||||
 | 
					                "etcd connection failed."
 | 
				
			||||||
 | 
					            ) from err
 | 
				
			||||||
        except etcd3.exceptions.ConnectionTimeoutError as err:
 | 
					        except etcd3.exceptions.ConnectionTimeoutError as err:
 | 
				
			||||||
            raise etcd3.exceptions.ConnectionTimeoutError('etcd connection timeout.') from err
 | 
					            raise etcd3.exceptions.ConnectionTimeoutError(
 | 
				
			||||||
 | 
					                "etcd connection timeout."
 | 
				
			||||||
 | 
					            ) from err
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            logger.exception('Some etcd error occured. See syslog for details.')
 | 
					            logger.exception(
 | 
				
			||||||
 | 
					                "Some etcd error occured. See syslog for details."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return wrapper
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,7 +63,7 @@ class Etcd3Wrapper:
 | 
				
			||||||
            _value = json.dumps(_value)
 | 
					            _value = json.dumps(_value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not isinstance(_key, str):
 | 
					        if not isinstance(_key, str):
 | 
				
			||||||
            _key = _key.decode('utf-8')
 | 
					            _key = _key.decode("utf-8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self.client.put(_key, _value, **kwargs)
 | 
					        return self.client.put(_key, _value, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,18 +77,25 @@ class Etcd3Wrapper:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @readable_errors
 | 
					    @readable_errors
 | 
				
			||||||
    def watch_prefix(self, key, timeout=0, value_in_json=False):
 | 
					    def watch_prefix(self, key, timeout=0, value_in_json=False):
 | 
				
			||||||
        timeout_event = EtcdEntry(PseudoEtcdMeta(key=b'TIMEOUT'),
 | 
					        timeout_event = EtcdEntry(
 | 
				
			||||||
                                  value=str.encode(json.dumps({'status': 'TIMEOUT',
 | 
					            PseudoEtcdMeta(key=b"TIMEOUT"),
 | 
				
			||||||
                                                               'type': 'TIMEOUT'})),
 | 
					            value=str.encode(
 | 
				
			||||||
                                  value_in_json=value_in_json)
 | 
					                json.dumps({"status": "TIMEOUT", "type": "TIMEOUT"})
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            value_in_json=value_in_json,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        event_queue = queue.Queue()
 | 
					        event_queue = queue.Queue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def add_event_to_queue(event):
 | 
					        def add_event_to_queue(event):
 | 
				
			||||||
            if hasattr(event, 'events'):
 | 
					            if hasattr(event, "events"):
 | 
				
			||||||
                for e in event.events:
 | 
					                for e in event.events:
 | 
				
			||||||
                    if e.value:
 | 
					                    if e.value:
 | 
				
			||||||
                        event_queue.put(EtcdEntry(e, e.value, value_in_json=value_in_json))
 | 
					                        event_queue.put(
 | 
				
			||||||
 | 
					                            EtcdEntry(
 | 
				
			||||||
 | 
					                                e, e.value, value_in_json=value_in_json
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.client.add_watch_prefix_callback(key, add_event_to_queue)
 | 
					        self.client.add_watch_prefix_callback(key, add_event_to_queue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -96,4 +110,8 @@ class Etcd3Wrapper:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PsuedoEtcdEntry(EtcdEntry):
 | 
					class PsuedoEtcdEntry(EtcdEntry):
 | 
				
			||||||
    def __init__(self, key, value, value_in_json=False):
 | 
					    def __init__(self, key, value, value_in_json=False):
 | 
				
			||||||
        super().__init__(PseudoEtcdMeta(key=key.encode('utf-8')), value, value_in_json=value_in_json)
 | 
					        super().__init__(
 | 
				
			||||||
 | 
					            PseudoEtcdMeta(key=key.encode("utf-8")),
 | 
				
			||||||
 | 
					            value,
 | 
				
			||||||
 | 
					            value_in_json=value_in_json,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,7 +29,9 @@ class HostEntry(SpecificEtcdEntryBase):
 | 
				
			||||||
        self.last_heartbeat = time.strftime("%Y-%m-%d %H:%M:%S")
 | 
					        self.last_heartbeat = time.strftime("%Y-%m-%d %H:%M:%S")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_alive(self):
 | 
					    def is_alive(self):
 | 
				
			||||||
        last_heartbeat = datetime.strptime(self.last_heartbeat, "%Y-%m-%d %H:%M:%S")
 | 
					        last_heartbeat = datetime.strptime(
 | 
				
			||||||
 | 
					            self.last_heartbeat, "%Y-%m-%d %H:%M:%S"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        delta = datetime.now() - last_heartbeat
 | 
					        delta = datetime.now() - last_heartbeat
 | 
				
			||||||
        if delta.total_seconds() > 60:
 | 
					        if delta.total_seconds() > 60:
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,21 +7,21 @@ class NoTracebackStreamHandler(logging.StreamHandler):
 | 
				
			||||||
        info, cache = record.exc_info, record.exc_text
 | 
					        info, cache = record.exc_info, record.exc_text
 | 
				
			||||||
        record.exc_info, record.exc_text = None, None
 | 
					        record.exc_info, record.exc_text = None, None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if record.levelname in ['WARNING', 'WARN']:
 | 
					        if record.levelname in ["WARNING", "WARN"]:
 | 
				
			||||||
            color = colorama.Fore.LIGHTYELLOW_EX
 | 
					            color = colorama.Fore.LIGHTYELLOW_EX
 | 
				
			||||||
        elif record.levelname == 'ERROR':
 | 
					        elif record.levelname == "ERROR":
 | 
				
			||||||
            color = colorama.Fore.LIGHTRED_EX
 | 
					            color = colorama.Fore.LIGHTRED_EX
 | 
				
			||||||
        elif record.levelname == 'INFO':
 | 
					        elif record.levelname == "INFO":
 | 
				
			||||||
            color = colorama.Fore.LIGHTGREEN_EX
 | 
					            color = colorama.Fore.LIGHTGREEN_EX
 | 
				
			||||||
        elif record.levelname == 'CRITICAL':
 | 
					        elif record.levelname == "CRITICAL":
 | 
				
			||||||
            color = colorama.Fore.LIGHTCYAN_EX
 | 
					            color = colorama.Fore.LIGHTCYAN_EX
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            color = colorama.Fore.WHITE
 | 
					            color = colorama.Fore.WHITE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            print(color, end='', flush=True)
 | 
					            print(color, end="", flush=True)
 | 
				
			||||||
            super().handle(record)
 | 
					            super().handle(record)
 | 
				
			||||||
        finally:
 | 
					        finally:
 | 
				
			||||||
            record.exc_info = info
 | 
					            record.exc_info = info
 | 
				
			||||||
            record.exc_text = cache
 | 
					            record.exc_text = cache
 | 
				
			||||||
            print(colorama.Style.RESET_ALL, end='', flush=True)
 | 
					            print(colorama.Style.RESET_ALL, end="", flush=True)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,9 @@ def random_bytes(num=6):
 | 
				
			||||||
    return [random.randrange(256) for _ in range(num)]
 | 
					    return [random.randrange(256) for _ in range(num)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_mac(uaa=False, multicast=False, oui=None, separator=':', byte_fmt='%02x'):
 | 
					def generate_mac(
 | 
				
			||||||
 | 
					    uaa=False, multicast=False, oui=None, separator=":", byte_fmt="%02x"
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
    mac = random_bytes()
 | 
					    mac = random_bytes()
 | 
				
			||||||
    if oui:
 | 
					    if oui:
 | 
				
			||||||
        if type(oui) == str:
 | 
					        if type(oui) == str:
 | 
				
			||||||
| 
						 | 
					@ -30,35 +32,51 @@ def generate_mac(uaa=False, multicast=False, oui=None, separator=':', byte_fmt='
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_dev(script, _id, dev, ip=None):
 | 
					def create_dev(script, _id, dev, ip=None):
 | 
				
			||||||
    command = ['sudo', '-p', 'Enter password to create network devices for vm: ',
 | 
					    command = [
 | 
				
			||||||
               script, str(_id), dev]
 | 
					        "sudo",
 | 
				
			||||||
 | 
					        "-p",
 | 
				
			||||||
 | 
					        "Enter password to create network devices for vm: ",
 | 
				
			||||||
 | 
					        script,
 | 
				
			||||||
 | 
					        str(_id),
 | 
				
			||||||
 | 
					        dev,
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
    if ip:
 | 
					    if ip:
 | 
				
			||||||
        command.append(ip)
 | 
					        command.append(ip)
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        output = sp.check_output(command, stderr=sp.PIPE)
 | 
					        output = sp.check_output(command, stderr=sp.PIPE)
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        logger.exception('Creation of interface %s failed.', dev)
 | 
					        logger.exception("Creation of interface %s failed.", dev)
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return output.decode('utf-8').strip()
 | 
					        return output.decode("utf-8").strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def delete_network_interface(iface):
 | 
					def delete_network_interface(iface):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        sp.check_output(
 | 
					        sp.check_output(
 | 
				
			||||||
            [
 | 
					            [
 | 
				
			||||||
                'sudo', '-p', 'Enter password to remove {} network device: '.format(iface),
 | 
					                "sudo",
 | 
				
			||||||
                'ip', 'link', 'del', iface
 | 
					                "-p",
 | 
				
			||||||
            ], stderr=sp.PIPE
 | 
					                "Enter password to remove {} network device: ".format(
 | 
				
			||||||
 | 
					                    iface
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                "ip",
 | 
				
			||||||
 | 
					                "link",
 | 
				
			||||||
 | 
					                "del",
 | 
				
			||||||
 | 
					                iface,
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            stderr=sp.PIPE,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        logger.exception('Interface %s Deletion failed', iface)
 | 
					        logger.exception("Interface %s Deletion failed", iface)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def find_free_port():
 | 
					def find_free_port():
 | 
				
			||||||
    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
 | 
					    with closing(
 | 
				
			||||||
 | 
					        socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					    ) as s:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            s.bind(('', 0))
 | 
					            s.bind(("", 0))
 | 
				
			||||||
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 | 
					            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,6 @@ class RequestType:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RequestEntry(SpecificEtcdEntryBase):
 | 
					class RequestEntry(SpecificEtcdEntryBase):
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, e):
 | 
					    def __init__(self, e):
 | 
				
			||||||
        self.destination_sock_path = None
 | 
					        self.destination_sock_path = None
 | 
				
			||||||
        self.destination_host_key = None
 | 
					        self.destination_host_key = None
 | 
				
			||||||
| 
						 | 
					@ -30,8 +29,11 @@ class RequestEntry(SpecificEtcdEntryBase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def from_scratch(cls, request_prefix, **kwargs):
 | 
					    def from_scratch(cls, request_prefix, **kwargs):
 | 
				
			||||||
        e = PsuedoEtcdEntry(join(request_prefix, uuid4().hex),
 | 
					        e = PsuedoEtcdEntry(
 | 
				
			||||||
                            value=json.dumps(kwargs).encode("utf-8"), value_in_json=True)
 | 
					            join(request_prefix, uuid4().hex),
 | 
				
			||||||
 | 
					            value=json.dumps(kwargs).encode("utf-8"),
 | 
				
			||||||
 | 
					            value_in_json=True,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        return cls(e)
 | 
					        return cls(e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ class StorageUnit(fields.Field):
 | 
				
			||||||
class SpecsSchema(Schema):
 | 
					class SpecsSchema(Schema):
 | 
				
			||||||
    cpu = fields.Int()
 | 
					    cpu = fields.Int()
 | 
				
			||||||
    ram = StorageUnit()
 | 
					    ram = StorageUnit()
 | 
				
			||||||
    os_ssd = StorageUnit(data_key='os-ssd', attribute='os-ssd')
 | 
					    os_ssd = StorageUnit(data_key="os-ssd", attribute="os-ssd")
 | 
				
			||||||
    hdd = fields.List(StorageUnit())
 | 
					    hdd = fields.List(StorageUnit())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,11 +29,13 @@ class VMSchema(Schema):
 | 
				
			||||||
    image_uuid = fields.Str()
 | 
					    image_uuid = fields.Str()
 | 
				
			||||||
    hostname = fields.Str()
 | 
					    hostname = fields.Str()
 | 
				
			||||||
    metadata = fields.Dict()
 | 
					    metadata = fields.Dict()
 | 
				
			||||||
    network = fields.List(fields.Tuple((fields.Str(), fields.Str(), fields.Int())))
 | 
					    network = fields.List(
 | 
				
			||||||
 | 
					        fields.Tuple((fields.Str(), fields.Str(), fields.Int()))
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    in_migration = fields.Bool()
 | 
					    in_migration = fields.Bool()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NetworkSchema(Schema):
 | 
					class NetworkSchema(Schema):
 | 
				
			||||||
    _id = fields.Int(data_key='id', attribute='id')
 | 
					    _id = fields.Int(data_key="id", attribute="id")
 | 
				
			||||||
    _type = fields.Str(data_key='type', attribute='type')
 | 
					    _type = fields.Str(data_key="type", attribute="type")
 | 
				
			||||||
    ipv6 = fields.Str()
 | 
					    ipv6 = fields.Str()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,7 @@ from ucloud.settings import settings as config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ImageStorageHandler(ABC):
 | 
					class ImageStorageHandler(ABC):
 | 
				
			||||||
    handler_name = 'base'
 | 
					    handler_name = "base"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, image_base, vm_base):
 | 
					    def __init__(self, image_base, vm_base):
 | 
				
			||||||
        self.image_base = image_base
 | 
					        self.image_base = image_base
 | 
				
			||||||
| 
						 | 
					@ -55,9 +55,9 @@ class ImageStorageHandler(ABC):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            sp.check_output(command, stderr=sp.PIPE)
 | 
					            sp.check_output(command, stderr=sp.PIPE)
 | 
				
			||||||
        except sp.CalledProcessError as e:
 | 
					        except sp.CalledProcessError as e:
 | 
				
			||||||
            _stderr = e.stderr.decode('utf-8').strip()
 | 
					            _stderr = e.stderr.decode("utf-8").strip()
 | 
				
			||||||
            if report:
 | 
					            if report:
 | 
				
			||||||
                logger.exception('%s:- %s', error_origin, _stderr)
 | 
					                logger.exception("%s:- %s", error_origin, _stderr)
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,14 +72,16 @@ class ImageStorageHandler(ABC):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FileSystemBasedImageStorageHandler(ImageStorageHandler):
 | 
					class FileSystemBasedImageStorageHandler(ImageStorageHandler):
 | 
				
			||||||
    handler_name = 'Filesystem'
 | 
					    handler_name = "Filesystem"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def import_image(self, src, dest, protect=False):
 | 
					    def import_image(self, src, dest, protect=False):
 | 
				
			||||||
        dest = join_path(self.image_base, dest)
 | 
					        dest = join_path(self.image_base, dest)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            shutil.copy(src, dest)
 | 
					            shutil.copy(src, dest)
 | 
				
			||||||
            if protect:
 | 
					            if protect:
 | 
				
			||||||
                os.chmod(dest, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
 | 
					                os.chmod(
 | 
				
			||||||
 | 
					                    dest, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            logger.exception(e)
 | 
					            logger.exception(e)
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
| 
						 | 
					@ -97,7 +99,14 @@ class FileSystemBasedImageStorageHandler(ImageStorageHandler):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def resize_vm_image(self, path, size):
 | 
					    def resize_vm_image(self, path, size):
 | 
				
			||||||
        path = join_path(self.vm_base, path)
 | 
					        path = join_path(self.vm_base, path)
 | 
				
			||||||
        command = ["qemu-img", "resize", "-f", "raw", path, "{}M".format(size)]
 | 
					        command = [
 | 
				
			||||||
 | 
					            "qemu-img",
 | 
				
			||||||
 | 
					            "resize",
 | 
				
			||||||
 | 
					            "-f",
 | 
				
			||||||
 | 
					            "raw",
 | 
				
			||||||
 | 
					            path,
 | 
				
			||||||
 | 
					            "{}M".format(size),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
        if self.execute_command(command):
 | 
					        if self.execute_command(command):
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -126,15 +135,25 @@ class FileSystemBasedImageStorageHandler(ImageStorageHandler):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CEPHBasedImageStorageHandler(ImageStorageHandler):
 | 
					class CEPHBasedImageStorageHandler(ImageStorageHandler):
 | 
				
			||||||
    handler_name = 'Ceph'
 | 
					    handler_name = "Ceph"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def import_image(self, src, dest, protect=False):
 | 
					    def import_image(self, src, dest, protect=False):
 | 
				
			||||||
        dest = join_path(self.image_base, dest)
 | 
					        dest = join_path(self.image_base, dest)
 | 
				
			||||||
        import_command = ["rbd", "import", src, dest]
 | 
					        import_command = ["rbd", "import", src, dest]
 | 
				
			||||||
        commands = [import_command]
 | 
					        commands = [import_command]
 | 
				
			||||||
        if protect:
 | 
					        if protect:
 | 
				
			||||||
            snap_create_command = ["rbd", "snap", "create", "{}@protected".format(dest)]
 | 
					            snap_create_command = [
 | 
				
			||||||
            snap_protect_command = ["rbd", "snap", "protect", "{}@protected".format(dest)]
 | 
					                "rbd",
 | 
				
			||||||
 | 
					                "snap",
 | 
				
			||||||
 | 
					                "create",
 | 
				
			||||||
 | 
					                "{}@protected".format(dest),
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					            snap_protect_command = [
 | 
				
			||||||
 | 
					                "rbd",
 | 
				
			||||||
 | 
					                "snap",
 | 
				
			||||||
 | 
					                "protect",
 | 
				
			||||||
 | 
					                "{}@protected".format(dest),
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
            commands.append(snap_create_command)
 | 
					            commands.append(snap_create_command)
 | 
				
			||||||
            commands.append(snap_protect_command)
 | 
					            commands.append(snap_protect_command)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -174,16 +193,16 @@ class CEPHBasedImageStorageHandler(ImageStorageHandler):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_storage_handler():
 | 
					def get_storage_handler():
 | 
				
			||||||
    __storage_backend = config['storage']['storage_backend']
 | 
					    __storage_backend = config["storage"]["storage_backend"]
 | 
				
			||||||
    if __storage_backend == 'filesystem':
 | 
					    if __storage_backend == "filesystem":
 | 
				
			||||||
        return FileSystemBasedImageStorageHandler(
 | 
					        return FileSystemBasedImageStorageHandler(
 | 
				
			||||||
            vm_base=config['storage']['vm_dir'],
 | 
					            vm_base=config["storage"]["vm_dir"],
 | 
				
			||||||
            image_base=config['storage']['image_dir']
 | 
					            image_base=config["storage"]["image_dir"],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    elif __storage_backend == 'ceph':
 | 
					    elif __storage_backend == "ceph":
 | 
				
			||||||
        return CEPHBasedImageStorageHandler(
 | 
					        return CEPHBasedImageStorageHandler(
 | 
				
			||||||
            vm_base=config['storage']['ceph_vm_pool'],
 | 
					            vm_base=config["storage"]["ceph_vm_pool"],
 | 
				
			||||||
            image_base=config['storage']['ceph_image_pool']
 | 
					            image_base=config["storage"]["ceph_image_pool"],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        raise Exception('Unknown Image Storage Handler')
 | 
					        raise Exception("Unknown Image Storage Handler")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,13 +13,12 @@ class VMStatus:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def declare_stopped(vm):
 | 
					def declare_stopped(vm):
 | 
				
			||||||
    vm['hostname'] = ''
 | 
					    vm["hostname"] = ""
 | 
				
			||||||
    vm['in_migration'] = False
 | 
					    vm["in_migration"] = False
 | 
				
			||||||
    vm['status'] = VMStatus.stopped
 | 
					    vm["status"] = VMStatus.stopped
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VMEntry(SpecificEtcdEntryBase):
 | 
					class VMEntry(SpecificEtcdEntryBase):
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, e):
 | 
					    def __init__(self, e):
 | 
				
			||||||
        self.owner = None  # type: str
 | 
					        self.owner = None  # type: str
 | 
				
			||||||
        self.specs = None  # type: dict
 | 
					        self.specs = None  # type: dict
 | 
				
			||||||
| 
						 | 
					@ -48,7 +47,9 @@ class VMEntry(SpecificEtcdEntryBase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add_log(self, msg):
 | 
					    def add_log(self, msg):
 | 
				
			||||||
        self.log = self.log[:5]
 | 
					        self.log = self.log[:5]
 | 
				
			||||||
        self.log.append("{} - {}".format(datetime.now().isoformat(), msg))
 | 
					        self.log.append(
 | 
				
			||||||
 | 
					            "{} - {}".format(datetime.now().isoformat(), msg)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VmPool:
 | 
					class VmPool:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,31 +5,41 @@ from ucloud.shared import shared
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def update_config(section, kwargs):
 | 
					def update_config(section, kwargs):
 | 
				
			||||||
    uncloud_config = shared.etcd_client.get(settings.config_key, value_in_json=True)
 | 
					    uncloud_config = shared.etcd_client.get(
 | 
				
			||||||
 | 
					        settings.config_key, value_in_json=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    if not uncloud_config:
 | 
					    if not uncloud_config:
 | 
				
			||||||
        uncloud_config = {}
 | 
					        uncloud_config = {}
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        uncloud_config = uncloud_config.value
 | 
					        uncloud_config = uncloud_config.value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    uncloud_config[section] = kwargs
 | 
					    uncloud_config[section] = kwargs
 | 
				
			||||||
    shared.etcd_client.put(settings.config_key, uncloud_config, value_in_json=True)
 | 
					    shared.etcd_client.put(
 | 
				
			||||||
 | 
					        settings.config_key, uncloud_config, value_in_json=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def configure_parser(parser):
 | 
					def configure_parser(parser):
 | 
				
			||||||
    configure_subparsers = parser.add_subparsers(dest="subcommand")
 | 
					    configure_subparsers = parser.add_subparsers(dest="subcommand")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    otp_parser = configure_subparsers.add_parser("otp")
 | 
					    otp_parser = configure_subparsers.add_parser("otp")
 | 
				
			||||||
    otp_parser.add_argument("--verification-controller-url",
 | 
					    otp_parser.add_argument(
 | 
				
			||||||
                            required=True, metavar="URL")
 | 
					        "--verification-controller-url", required=True, metavar="URL"
 | 
				
			||||||
    otp_parser.add_argument("--auth-name", required=True,
 | 
					    )
 | 
				
			||||||
                            metavar="OTP-NAME")
 | 
					    otp_parser.add_argument(
 | 
				
			||||||
    otp_parser.add_argument("--auth-realm", required=True,
 | 
					        "--auth-name", required=True, metavar="OTP-NAME"
 | 
				
			||||||
                            metavar="OTP-REALM")
 | 
					    )
 | 
				
			||||||
    otp_parser.add_argument("--auth-seed", required=True,
 | 
					    otp_parser.add_argument(
 | 
				
			||||||
                            metavar="OTP-SEED")
 | 
					        "--auth-realm", required=True, metavar="OTP-REALM"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    otp_parser.add_argument(
 | 
				
			||||||
 | 
					        "--auth-seed", required=True, metavar="OTP-SEED"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    network_parser = configure_subparsers.add_parser("network")
 | 
					    network_parser = configure_subparsers.add_parser("network")
 | 
				
			||||||
    network_parser.add_argument("--prefix-length", required=True, type=int)
 | 
					    network_parser.add_argument(
 | 
				
			||||||
 | 
					        "--prefix-length", required=True, type=int
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    network_parser.add_argument("--prefix", required=True)
 | 
					    network_parser.add_argument("--prefix", required=True)
 | 
				
			||||||
    network_parser.add_argument("--vxlan-phy-dev", required=True)
 | 
					    network_parser.add_argument("--vxlan-phy-dev", required=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,25 +48,31 @@ def configure_parser(parser):
 | 
				
			||||||
    netbox_parser.add_argument("--token", required=True)
 | 
					    netbox_parser.add_argument("--token", required=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ssh_parser = configure_subparsers.add_parser("ssh")
 | 
					    ssh_parser = configure_subparsers.add_parser("ssh")
 | 
				
			||||||
    ssh_parser.add_argument('--username', default="root")
 | 
					    ssh_parser.add_argument("--username", default="root")
 | 
				
			||||||
    ssh_parser.add_argument('--private-key-path',
 | 
					    ssh_parser.add_argument(
 | 
				
			||||||
                            default=os.path.expanduser("~/.ssh/id_rsa"))
 | 
					        "--private-key-path",
 | 
				
			||||||
 | 
					        default=os.path.expanduser("~/.ssh/id_rsa"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    storage_parser = configure_subparsers.add_parser("storage")
 | 
					    storage_parser = configure_subparsers.add_parser("storage")
 | 
				
			||||||
    storage_parser.add_argument('--file-dir', required=True)
 | 
					    storage_parser.add_argument("--file-dir", required=True)
 | 
				
			||||||
    storage_parser_subparsers = storage_parser.add_subparsers(dest="storage_backend")
 | 
					    storage_parser_subparsers = storage_parser.add_subparsers(
 | 
				
			||||||
 | 
					        dest="storage_backend"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    filesystem_storage_parser = storage_parser_subparsers.add_parser("filesystem")
 | 
					    filesystem_storage_parser = storage_parser_subparsers.add_parser(
 | 
				
			||||||
    filesystem_storage_parser.add_argument('--vm-dir', required=True)
 | 
					        "filesystem"
 | 
				
			||||||
    filesystem_storage_parser.add_argument('--image-dir', required=True)
 | 
					    )
 | 
				
			||||||
 | 
					    filesystem_storage_parser.add_argument("--vm-dir", required=True)
 | 
				
			||||||
 | 
					    filesystem_storage_parser.add_argument("--image-dir", required=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ceph_storage_parser = storage_parser_subparsers.add_parser("ceph")
 | 
					    ceph_storage_parser = storage_parser_subparsers.add_parser("ceph")
 | 
				
			||||||
    ceph_storage_parser.add_argument('--ceph-vm-pool', required=True)
 | 
					    ceph_storage_parser.add_argument("--ceph-vm-pool", required=True)
 | 
				
			||||||
    ceph_storage_parser.add_argument('--ceph-image-pool', required=True)
 | 
					    ceph_storage_parser.add_argument("--ceph-image-pool", required=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main(**kwargs):
 | 
					def main(**kwargs):
 | 
				
			||||||
    subcommand = kwargs.pop('subcommand')
 | 
					    subcommand = kwargs.pop("subcommand")
 | 
				
			||||||
    if not subcommand:
 | 
					    if not subcommand:
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,9 +17,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# -- Project information -----------------------------------------------------
 | 
					# -- Project information -----------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
project = 'ucloud'
 | 
					project = "ucloud"
 | 
				
			||||||
copyright = '2019, ungleich'
 | 
					copyright = "2019, ungleich"
 | 
				
			||||||
author = 'ungleich'
 | 
					author = "ungleich"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# -- General configuration ---------------------------------------------------
 | 
					# -- General configuration ---------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,12 +27,12 @@ author = 'ungleich'
 | 
				
			||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 | 
					# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 | 
				
			||||||
# ones.
 | 
					# ones.
 | 
				
			||||||
extensions = [
 | 
					extensions = [
 | 
				
			||||||
    'sphinx.ext.autodoc',
 | 
					    "sphinx.ext.autodoc",
 | 
				
			||||||
    'sphinx_rtd_theme',
 | 
					    "sphinx_rtd_theme",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Add any paths that contain templates here, relative to this directory.
 | 
					# Add any paths that contain templates here, relative to this directory.
 | 
				
			||||||
templates_path = ['_templates']
 | 
					templates_path = ["_templates"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# List of patterns, relative to source directory, that match files and
 | 
					# List of patterns, relative to source directory, that match files and
 | 
				
			||||||
# directories to ignore when looking for source files.
 | 
					# directories to ignore when looking for source files.
 | 
				
			||||||
| 
						 | 
					@ -50,4 +50,4 @@ html_theme = "sphinx_rtd_theme"
 | 
				
			||||||
# Add any paths that contain custom static files (such as style sheets) here,
 | 
					# Add any paths that contain custom static files (such as style sheets) here,
 | 
				
			||||||
# relative to this directory. They are copied after the builtin static files,
 | 
					# relative to this directory. They are copied after the builtin static files,
 | 
				
			||||||
# so a file named "default.css" will overwrite the builtin "default.css".
 | 
					# so a file named "default.css" will overwrite the builtin "default.css".
 | 
				
			||||||
html_static_path = ['_static']
 | 
					html_static_path = ["_static"]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,8 @@ def sha512sum(file: str):
 | 
				
			||||||
       ELSE:
 | 
					       ELSE:
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if not isinstance(file, str): raise TypeError
 | 
					    if not isinstance(file, str):
 | 
				
			||||||
 | 
					        raise TypeError
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        output = sp.check_output(["sha512sum", file], stderr=sp.PIPE)
 | 
					        output = sp.check_output(["sha512sum", file], stderr=sp.PIPE)
 | 
				
			||||||
    except sp.CalledProcessError as e:
 | 
					    except sp.CalledProcessError as e:
 | 
				
			||||||
| 
						 | 
					@ -49,23 +50,25 @@ def track_file(file, base_dir):
 | 
				
			||||||
    file_path = pathlib.Path(file).parts[-1]
 | 
					    file_path = pathlib.Path(file).parts[-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Create Entry
 | 
					    # Create Entry
 | 
				
			||||||
    entry_key = os.path.join(settings['etcd']['file_prefix'], str(file_id))
 | 
					    entry_key = os.path.join(
 | 
				
			||||||
 | 
					        settings["etcd"]["file_prefix"], str(file_id)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    entry_value = {
 | 
					    entry_value = {
 | 
				
			||||||
        "filename": file_path,
 | 
					        "filename": file_path,
 | 
				
			||||||
        "owner": owner,
 | 
					        "owner": owner,
 | 
				
			||||||
        "sha512sum": sha512sum(file),
 | 
					        "sha512sum": sha512sum(file),
 | 
				
			||||||
        "creation_date": creation_date,
 | 
					        "creation_date": creation_date,
 | 
				
			||||||
        "size": os.path.getsize(file)
 | 
					        "size": os.path.getsize(file),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.info("Tracking %s", file)
 | 
					    logger.info("Tracking %s", file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    shared.etcd_client.put(entry_key, entry_value, value_in_json=True)
 | 
					    shared.etcd_client.put(entry_key, entry_value, value_in_json=True)
 | 
				
			||||||
    os.setxattr(file, 'user.utracked', b'True')
 | 
					    os.setxattr(file, "user.utracked", b"True")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
    base_dir = settings['storage']['file_dir']
 | 
					    base_dir = settings["storage"]["file_dir"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Recursively Get All Files and Folder below BASE_DIR
 | 
					    # Recursively Get All Files and Folder below BASE_DIR
 | 
				
			||||||
    files = glob.glob("{}/**".format(base_dir), recursive=True)
 | 
					    files = glob.glob("{}/**".format(base_dir), recursive=True)
 | 
				
			||||||
| 
						 | 
					@ -76,7 +79,7 @@ def main():
 | 
				
			||||||
    untracked_files = []
 | 
					    untracked_files = []
 | 
				
			||||||
    for file in files:
 | 
					    for file in files:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            os.getxattr(file, 'user.utracked')
 | 
					            os.getxattr(file, "user.utracked")
 | 
				
			||||||
        except OSError:
 | 
					        except OSError:
 | 
				
			||||||
            track_file(file, base_dir)
 | 
					            track_file(file, base_dir)
 | 
				
			||||||
            untracked_files.append(file)
 | 
					            untracked_files.append(file)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,49 +15,79 @@ from . import virtualmachine, logger
 | 
				
			||||||
def update_heartbeat(hostname):
 | 
					def update_heartbeat(hostname):
 | 
				
			||||||
    """Update Last HeartBeat Time for :param hostname: in etcd"""
 | 
					    """Update Last HeartBeat Time for :param hostname: in etcd"""
 | 
				
			||||||
    host_pool = shared.host_pool
 | 
					    host_pool = shared.host_pool
 | 
				
			||||||
    this_host = next(filter(lambda h: h.hostname == hostname, host_pool.hosts), None)
 | 
					    this_host = next(
 | 
				
			||||||
 | 
					        filter(lambda h: h.hostname == hostname, host_pool.hosts), None
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    while True:
 | 
					    while True:
 | 
				
			||||||
        this_host.update_heartbeat()
 | 
					        this_host.update_heartbeat()
 | 
				
			||||||
        host_pool.put(this_host)
 | 
					        host_pool.put(this_host)
 | 
				
			||||||
        time.sleep(10)
 | 
					        time.sleep(10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def maintenance():
 | 
					def maintenance(host):
 | 
				
			||||||
    vmm = VMM()
 | 
					    vmm = VMM()
 | 
				
			||||||
    running_vms = vmm.discover()
 | 
					    running_vms = vmm.discover()
 | 
				
			||||||
    for vm_uuid in running_vms:
 | 
					    for vm_uuid in running_vms:
 | 
				
			||||||
        if vmm.is_running(vm_uuid) and vmm.get_status(vm_uuid) == 'running':
 | 
					        if (
 | 
				
			||||||
            vm = shared.vm_pool.get(join_path(settings['etcd']['vm_prefix'], vm_uuid))
 | 
					            vmm.is_running(vm_uuid)
 | 
				
			||||||
 | 
					            and vmm.get_status(vm_uuid) == "running"
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            vm = shared.vm_pool.get(
 | 
				
			||||||
 | 
					                join_path(settings["etcd"]["vm_prefix"], vm_uuid)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            vm.status = VMStatus.running
 | 
					            vm.status = VMStatus.running
 | 
				
			||||||
 | 
					            vm.vnc_socket = vmm.get_vnc(vm_uuid)
 | 
				
			||||||
 | 
					            vm.hostname = host
 | 
				
			||||||
            shared.vm_pool.put(vm)
 | 
					            shared.vm_pool.put(vm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main(hostname):
 | 
					def main(hostname):
 | 
				
			||||||
    host_pool = shared.host_pool
 | 
					    host_pool = shared.host_pool
 | 
				
			||||||
    host = next(filter(lambda h: h.hostname == hostname, host_pool.hosts), None)
 | 
					    host = next(
 | 
				
			||||||
    assert host is not None, "No such host with name = {}".format(hostname)
 | 
					        filter(lambda h: h.hostname == hostname, host_pool.hosts), None
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    assert host is not None, "No such host with name = {}".format(
 | 
				
			||||||
 | 
					        hostname
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        heartbeat_updating_process = mp.Process(target=update_heartbeat, args=(hostname,))
 | 
					        heartbeat_updating_process = mp.Process(
 | 
				
			||||||
 | 
					            target=update_heartbeat, args=(hostname,)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        heartbeat_updating_process.start()
 | 
					        heartbeat_updating_process.start()
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        raise Exception('ucloud-host heartbeat updating mechanism is not working') from e
 | 
					        raise Exception(
 | 
				
			||||||
 | 
					            "ucloud-host heartbeat updating mechanism is not working"
 | 
				
			||||||
 | 
					        ) from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for events_iterator in [
 | 
					    for events_iterator in [
 | 
				
			||||||
        shared.etcd_client.get_prefix(settings['etcd']['request_prefix'], value_in_json=True),
 | 
					        shared.etcd_client.get_prefix(
 | 
				
			||||||
        shared.etcd_client.watch_prefix(settings['etcd']['request_prefix'], timeout=10, value_in_json=True),
 | 
					            settings["etcd"]["request_prefix"], value_in_json=True
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        shared.etcd_client.watch_prefix(
 | 
				
			||||||
 | 
					            settings["etcd"]["request_prefix"],
 | 
				
			||||||
 | 
					            timeout=10,
 | 
				
			||||||
 | 
					            value_in_json=True,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
    ]:
 | 
					    ]:
 | 
				
			||||||
        for request_event in events_iterator:
 | 
					        for request_event in events_iterator:
 | 
				
			||||||
            request_event = RequestEntry(request_event)
 | 
					            request_event = RequestEntry(request_event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if request_event.type == "TIMEOUT":
 | 
					            if request_event.type == "TIMEOUT":
 | 
				
			||||||
                maintenance()
 | 
					                maintenance(host.key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if request_event.hostname == host.key:
 | 
					            if request_event.hostname == host.key:
 | 
				
			||||||
                logger.debug("VM Request: %s", request_event)
 | 
					                logger.debug("VM Request: %s", request_event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                shared.request_pool.client.client.delete(request_event.key)
 | 
					                shared.request_pool.client.client.delete(
 | 
				
			||||||
                vm_entry = shared.etcd_client.get(join_path(settings['etcd']['vm_prefix'], request_event.uuid))
 | 
					                    request_event.key
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                vm_entry = shared.etcd_client.get(
 | 
				
			||||||
 | 
					                    join_path(
 | 
				
			||||||
 | 
					                        settings["etcd"]["vm_prefix"],
 | 
				
			||||||
 | 
					                        request_event.uuid,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if vm_entry:
 | 
					                if vm_entry:
 | 
				
			||||||
                    vm = virtualmachine.VM(vm_entry)
 | 
					                    vm = virtualmachine.VM(vm_entry)
 | 
				
			||||||
| 
						 | 
					@ -70,23 +100,35 @@ def main(hostname):
 | 
				
			||||||
                    elif request_event.type == RequestType.DeleteVM:
 | 
					                    elif request_event.type == RequestType.DeleteVM:
 | 
				
			||||||
                        vm.delete()
 | 
					                        vm.delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    elif request_event.type == RequestType.InitVMMigration:
 | 
					                    elif (
 | 
				
			||||||
 | 
					                        request_event.type
 | 
				
			||||||
 | 
					                        == RequestType.InitVMMigration
 | 
				
			||||||
 | 
					                    ):
 | 
				
			||||||
                        vm.start(destination_host_key=host.key)
 | 
					                        vm.start(destination_host_key=host.key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    elif request_event.type == RequestType.TransferVM:
 | 
					                    elif request_event.type == RequestType.TransferVM:
 | 
				
			||||||
                        host = host_pool.get(request_event.destination_host_key)
 | 
					                        host = host_pool.get(
 | 
				
			||||||
 | 
					                            request_event.destination_host_key
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
                        if host:
 | 
					                        if host:
 | 
				
			||||||
                            vm.migrate(destination_host=host.hostname,
 | 
					                            vm.migrate(
 | 
				
			||||||
                                       destination_sock_path=request_event.destination_sock_path)
 | 
					                                destination_host=host.hostname,
 | 
				
			||||||
 | 
					                                destination_sock_path=request_event.destination_sock_path,
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
                        else:
 | 
					                        else:
 | 
				
			||||||
                            logger.error('Host %s not found!', request_event.destination_host_key)
 | 
					                            logger.error(
 | 
				
			||||||
 | 
					                                "Host %s not found!",
 | 
				
			||||||
 | 
					                                request_event.destination_host_key,
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    logger.info("VM Entry missing")
 | 
					                    logger.info("VM Entry missing")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    argparser = argparse.ArgumentParser()
 | 
					    argparser = argparse.ArgumentParser()
 | 
				
			||||||
    argparser.add_argument("hostname", help="Name of this host. e.g uncloud1.ungleich.ch")
 | 
					    argparser.add_argument(
 | 
				
			||||||
 | 
					        "hostname", help="Name of this host. e.g uncloud1.ungleich.ch"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    args = argparser.parse_args()
 | 
					    args = argparser.parse_args()
 | 
				
			||||||
    mp.set_start_method('spawn')
 | 
					    mp.set_start_method("spawn")
 | 
				
			||||||
    main(args.hostname)
 | 
					    main(args.hostname)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,10 +26,7 @@ LOG = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Mapping host architecture to any additional architectures it can
 | 
					# Mapping host architecture to any additional architectures it can
 | 
				
			||||||
# support which often includes its 32 bit cousin.
 | 
					# support which often includes its 32 bit cousin.
 | 
				
			||||||
ADDITIONAL_ARCHES = {
 | 
					ADDITIONAL_ARCHES = {"x86_64": "i386", "aarch64": "armhf"}
 | 
				
			||||||
    "x86_64": "i386",
 | 
					 | 
				
			||||||
    "aarch64": "armhf"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def kvm_available(target_arch=None):
 | 
					def kvm_available(target_arch=None):
 | 
				
			||||||
| 
						 | 
					@ -81,10 +78,17 @@ class QEMUMachine(object):
 | 
				
			||||||
        # vm is guaranteed to be shut down here
 | 
					        # vm is guaranteed to be shut down here
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, binary, args=None, wrapper=None, name=None,
 | 
					    def __init__(
 | 
				
			||||||
                 test_dir="/var/tmp", monitor_address=None,
 | 
					        self,
 | 
				
			||||||
                 socket_scm_helper=None):
 | 
					        binary,
 | 
				
			||||||
        '''
 | 
					        args=None,
 | 
				
			||||||
 | 
					        wrapper=None,
 | 
				
			||||||
 | 
					        name=None,
 | 
				
			||||||
 | 
					        test_dir="/var/tmp",
 | 
				
			||||||
 | 
					        monitor_address=None,
 | 
				
			||||||
 | 
					        socket_scm_helper=None,
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        Initialize a QEMUMachine
 | 
					        Initialize a QEMUMachine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @param binary: path to the qemu binary
 | 
					        @param binary: path to the qemu binary
 | 
				
			||||||
| 
						 | 
					@ -95,7 +99,7 @@ class QEMUMachine(object):
 | 
				
			||||||
        @param monitor_address: address for QMP monitor
 | 
					        @param monitor_address: address for QMP monitor
 | 
				
			||||||
        @param socket_scm_helper: helper program, required for send_fd_scm()
 | 
					        @param socket_scm_helper: helper program, required for send_fd_scm()
 | 
				
			||||||
        @note: Qemu process is not started until launch() is used.
 | 
					        @note: Qemu process is not started until launch() is used.
 | 
				
			||||||
        '''
 | 
					        """
 | 
				
			||||||
        if args is None:
 | 
					        if args is None:
 | 
				
			||||||
            args = []
 | 
					            args = []
 | 
				
			||||||
        if wrapper is None:
 | 
					        if wrapper is None:
 | 
				
			||||||
| 
						 | 
					@ -109,7 +113,9 @@ class QEMUMachine(object):
 | 
				
			||||||
        self._qemu_log_file = None
 | 
					        self._qemu_log_file = None
 | 
				
			||||||
        self._popen = None
 | 
					        self._popen = None
 | 
				
			||||||
        self._binary = binary
 | 
					        self._binary = binary
 | 
				
			||||||
        self._args = list(args)  # Force copy args in case we modify them
 | 
					        self._args = list(
 | 
				
			||||||
 | 
					            args
 | 
				
			||||||
 | 
					        )  # Force copy args in case we modify them
 | 
				
			||||||
        self._wrapper = wrapper
 | 
					        self._wrapper = wrapper
 | 
				
			||||||
        self._events = []
 | 
					        self._events = []
 | 
				
			||||||
        self._iolog = None
 | 
					        self._iolog = None
 | 
				
			||||||
| 
						 | 
					@ -137,26 +143,24 @@ class QEMUMachine(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # This can be used to add an unused monitor instance.
 | 
					    # This can be used to add an unused monitor instance.
 | 
				
			||||||
    def add_monitor_null(self):
 | 
					    def add_monitor_null(self):
 | 
				
			||||||
        self._args.append('-monitor')
 | 
					        self._args.append("-monitor")
 | 
				
			||||||
        self._args.append('null')
 | 
					        self._args.append("null")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add_fd(self, fd, fdset, opaque, opts=''):
 | 
					    def add_fd(self, fd, fdset, opaque, opts=""):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Pass a file descriptor to the VM
 | 
					        Pass a file descriptor to the VM
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        options = ['fd=%d' % fd,
 | 
					        options = ["fd=%d" % fd, "set=%d" % fdset, "opaque=%s" % opaque]
 | 
				
			||||||
                   'set=%d' % fdset,
 | 
					 | 
				
			||||||
                   'opaque=%s' % opaque]
 | 
					 | 
				
			||||||
        if opts:
 | 
					        if opts:
 | 
				
			||||||
            options.append(opts)
 | 
					            options.append(opts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # This did not exist before 3.4, but since then it is
 | 
					        # This did not exist before 3.4, but since then it is
 | 
				
			||||||
        # mandatory for our purpose
 | 
					        # mandatory for our purpose
 | 
				
			||||||
        if hasattr(os, 'set_inheritable'):
 | 
					        if hasattr(os, "set_inheritable"):
 | 
				
			||||||
            os.set_inheritable(fd, True)
 | 
					            os.set_inheritable(fd, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._args.append('-add-fd')
 | 
					        self._args.append("-add-fd")
 | 
				
			||||||
        self._args.append(','.join(options))
 | 
					        self._args.append(",".join(options))
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Exactly one of fd and file_path must be given.
 | 
					    # Exactly one of fd and file_path must be given.
 | 
				
			||||||
| 
						 | 
					@ -168,18 +172,21 @@ class QEMUMachine(object):
 | 
				
			||||||
        if self._socket_scm_helper is None:
 | 
					        if self._socket_scm_helper is None:
 | 
				
			||||||
            raise QEMUMachineError("No path to socket_scm_helper set")
 | 
					            raise QEMUMachineError("No path to socket_scm_helper set")
 | 
				
			||||||
        if not os.path.exists(self._socket_scm_helper):
 | 
					        if not os.path.exists(self._socket_scm_helper):
 | 
				
			||||||
            raise QEMUMachineError("%s does not exist" %
 | 
					            raise QEMUMachineError(
 | 
				
			||||||
                                   self._socket_scm_helper)
 | 
					                "%s does not exist" % self._socket_scm_helper
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # This did not exist before 3.4, but since then it is
 | 
					        # This did not exist before 3.4, but since then it is
 | 
				
			||||||
        # mandatory for our purpose
 | 
					        # mandatory for our purpose
 | 
				
			||||||
        if hasattr(os, 'set_inheritable'):
 | 
					        if hasattr(os, "set_inheritable"):
 | 
				
			||||||
            os.set_inheritable(self._qmp.get_sock_fd(), True)
 | 
					            os.set_inheritable(self._qmp.get_sock_fd(), True)
 | 
				
			||||||
            if fd is not None:
 | 
					            if fd is not None:
 | 
				
			||||||
                os.set_inheritable(fd, True)
 | 
					                os.set_inheritable(fd, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fd_param = ["%s" % self._socket_scm_helper,
 | 
					        fd_param = [
 | 
				
			||||||
                    "%d" % self._qmp.get_sock_fd()]
 | 
					            "%s" % self._socket_scm_helper,
 | 
				
			||||||
 | 
					            "%d" % self._qmp.get_sock_fd(),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if file_path is not None:
 | 
					        if file_path is not None:
 | 
				
			||||||
            assert fd is None
 | 
					            assert fd is None
 | 
				
			||||||
| 
						 | 
					@ -188,9 +195,14 @@ class QEMUMachine(object):
 | 
				
			||||||
            assert fd is not None
 | 
					            assert fd is not None
 | 
				
			||||||
            fd_param.append(str(fd))
 | 
					            fd_param.append(str(fd))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        devnull = open(os.path.devnull, 'rb')
 | 
					        devnull = open(os.path.devnull, "rb")
 | 
				
			||||||
        proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
 | 
					        proc = subprocess.Popen(
 | 
				
			||||||
                                stderr=subprocess.STDOUT, close_fds=False)
 | 
					            fd_param,
 | 
				
			||||||
 | 
					            stdin=devnull,
 | 
				
			||||||
 | 
					            stdout=subprocess.PIPE,
 | 
				
			||||||
 | 
					            stderr=subprocess.STDOUT,
 | 
				
			||||||
 | 
					            close_fds=False,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        output = proc.communicate()[0]
 | 
					        output = proc.communicate()[0]
 | 
				
			||||||
        if output:
 | 
					        if output:
 | 
				
			||||||
            LOG.debug(output)
 | 
					            LOG.debug(output)
 | 
				
			||||||
| 
						 | 
					@ -231,24 +243,29 @@ class QEMUMachine(object):
 | 
				
			||||||
        if isinstance(self._monitor_address, tuple):
 | 
					        if isinstance(self._monitor_address, tuple):
 | 
				
			||||||
            moncdev = "socket,id=mon,host=%s,port=%s" % (
 | 
					            moncdev = "socket,id=mon,host=%s,port=%s" % (
 | 
				
			||||||
                self._monitor_address[0],
 | 
					                self._monitor_address[0],
 | 
				
			||||||
                self._monitor_address[1])
 | 
					                self._monitor_address[1],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
 | 
					            moncdev = "socket,id=mon,path=%s" % self._vm_monitor
 | 
				
			||||||
        args = ['-chardev', moncdev,
 | 
					        args = ["-chardev", moncdev, "-mon", "chardev=mon,mode=control"]
 | 
				
			||||||
                '-mon', 'chardev=mon,mode=control']
 | 
					 | 
				
			||||||
        if self._machine is not None:
 | 
					        if self._machine is not None:
 | 
				
			||||||
            args.extend(['-machine', self._machine])
 | 
					            args.extend(["-machine", self._machine])
 | 
				
			||||||
        if self._console_set:
 | 
					        if self._console_set:
 | 
				
			||||||
            self._console_address = os.path.join(self._temp_dir,
 | 
					            self._console_address = os.path.join(
 | 
				
			||||||
                                                 self._name + "-console.sock")
 | 
					                self._temp_dir, self._name + "-console.sock"
 | 
				
			||||||
            chardev = ('socket,id=console,path=%s,server,nowait' %
 | 
					            )
 | 
				
			||||||
                       self._console_address)
 | 
					            chardev = (
 | 
				
			||||||
            args.extend(['-chardev', chardev])
 | 
					                "socket,id=console,path=%s,server,nowait"
 | 
				
			||||||
 | 
					                % self._console_address
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            args.extend(["-chardev", chardev])
 | 
				
			||||||
            if self._console_device_type is None:
 | 
					            if self._console_device_type is None:
 | 
				
			||||||
                args.extend(['-serial', 'chardev:console'])
 | 
					                args.extend(["-serial", "chardev:console"])
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                device = '%s,chardev=console' % self._console_device_type
 | 
					                device = (
 | 
				
			||||||
                args.extend(['-device', device])
 | 
					                    "%s,chardev=console" % self._console_device_type
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                args.extend(["-device", device])
 | 
				
			||||||
        return args
 | 
					        return args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _pre_launch(self):
 | 
					    def _pre_launch(self):
 | 
				
			||||||
| 
						 | 
					@ -256,13 +273,17 @@ class QEMUMachine(object):
 | 
				
			||||||
        if self._monitor_address is not None:
 | 
					        if self._monitor_address is not None:
 | 
				
			||||||
            self._vm_monitor = self._monitor_address
 | 
					            self._vm_monitor = self._monitor_address
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self._vm_monitor = os.path.join(self._temp_dir,
 | 
					            self._vm_monitor = os.path.join(
 | 
				
			||||||
                                            self._name + "-monitor.sock")
 | 
					                self._temp_dir, self._name + "-monitor.sock"
 | 
				
			||||||
        self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
 | 
					            )
 | 
				
			||||||
        self._qemu_log_file = open(self._qemu_log_path, 'wb')
 | 
					        self._qemu_log_path = os.path.join(
 | 
				
			||||||
 | 
					            self._temp_dir, self._name + ".log"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self._qemu_log_file = open(self._qemu_log_path, "wb")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor,
 | 
					        self._qmp = qmp.QEMUMonitorProtocol(
 | 
				
			||||||
                                            server=True)
 | 
					            self._vm_monitor, server=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _post_launch(self):
 | 
					    def _post_launch(self):
 | 
				
			||||||
        self._qmp.accept()
 | 
					        self._qmp.accept()
 | 
				
			||||||
| 
						 | 
					@ -289,7 +310,7 @@ class QEMUMachine(object):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self._launched:
 | 
					        if self._launched:
 | 
				
			||||||
            raise QEMUMachineError('VM already launched')
 | 
					            raise QEMUMachineError("VM already launched")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._iolog = None
 | 
					        self._iolog = None
 | 
				
			||||||
        self._qemu_full_args = None
 | 
					        self._qemu_full_args = None
 | 
				
			||||||
| 
						 | 
					@ -299,11 +320,11 @@ class QEMUMachine(object):
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            self.shutdown()
 | 
					            self.shutdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            LOG.debug('Error launching VM')
 | 
					            LOG.debug("Error launching VM")
 | 
				
			||||||
            if self._qemu_full_args:
 | 
					            if self._qemu_full_args:
 | 
				
			||||||
                LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
 | 
					                LOG.debug("Command: %r", " ".join(self._qemu_full_args))
 | 
				
			||||||
            if self._iolog:
 | 
					            if self._iolog:
 | 
				
			||||||
                LOG.debug('Output: %r', self._iolog)
 | 
					                LOG.debug("Output: %r", self._iolog)
 | 
				
			||||||
                raise Exception(self._iolog)
 | 
					                raise Exception(self._iolog)
 | 
				
			||||||
            raise
 | 
					            raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -311,17 +332,25 @@ class QEMUMachine(object):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Launch the VM and establish a QMP connection
 | 
					        Launch the VM and establish a QMP connection
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        devnull = open(os.path.devnull, 'rb')
 | 
					        devnull = open(os.path.devnull, "rb")
 | 
				
			||||||
        self._pre_launch()
 | 
					        self._pre_launch()
 | 
				
			||||||
        self._qemu_full_args = (self._wrapper + [self._binary] +
 | 
					        self._qemu_full_args = (
 | 
				
			||||||
                                self._base_args() + self._args)
 | 
					            self._wrapper
 | 
				
			||||||
        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
 | 
					            + [self._binary]
 | 
				
			||||||
        self._popen = subprocess.Popen(self._qemu_full_args,
 | 
					            + self._base_args()
 | 
				
			||||||
 | 
					            + self._args
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        LOG.debug(
 | 
				
			||||||
 | 
					            "VM launch command: %r", " ".join(self._qemu_full_args)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self._popen = subprocess.Popen(
 | 
				
			||||||
 | 
					            self._qemu_full_args,
 | 
				
			||||||
            stdin=devnull,
 | 
					            stdin=devnull,
 | 
				
			||||||
            stdout=self._qemu_log_file,
 | 
					            stdout=self._qemu_log_file,
 | 
				
			||||||
            stderr=subprocess.STDOUT,
 | 
					            stderr=subprocess.STDOUT,
 | 
				
			||||||
            shell=False,
 | 
					            shell=False,
 | 
				
			||||||
                                       close_fds=False)
 | 
					            close_fds=False,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        self._post_launch()
 | 
					        self._post_launch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def wait(self):
 | 
					    def wait(self):
 | 
				
			||||||
| 
						 | 
					@ -339,7 +368,7 @@ class QEMUMachine(object):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if self.is_running():
 | 
					        if self.is_running():
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                self._qmp.cmd('quit')
 | 
					                self._qmp.cmd("quit")
 | 
				
			||||||
                self._qmp.close()
 | 
					                self._qmp.close()
 | 
				
			||||||
            except:
 | 
					            except:
 | 
				
			||||||
                self._popen.kill()
 | 
					                self._popen.kill()
 | 
				
			||||||
| 
						 | 
					@ -350,11 +379,11 @@ class QEMUMachine(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        exitcode = self.exitcode()
 | 
					        exitcode = self.exitcode()
 | 
				
			||||||
        if exitcode is not None and exitcode < 0:
 | 
					        if exitcode is not None and exitcode < 0:
 | 
				
			||||||
            msg = 'qemu received signal %i: %s'
 | 
					            msg = "qemu received signal %i: %s"
 | 
				
			||||||
            if self._qemu_full_args:
 | 
					            if self._qemu_full_args:
 | 
				
			||||||
                command = ' '.join(self._qemu_full_args)
 | 
					                command = " ".join(self._qemu_full_args)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                command = ''
 | 
					                command = ""
 | 
				
			||||||
            LOG.warn(msg, -exitcode, command)
 | 
					            LOG.warn(msg, -exitcode, command)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._launched = False
 | 
					        self._launched = False
 | 
				
			||||||
| 
						 | 
					@ -366,7 +395,7 @@ class QEMUMachine(object):
 | 
				
			||||||
        qmp_args = dict()
 | 
					        qmp_args = dict()
 | 
				
			||||||
        for key, value in args.items():
 | 
					        for key, value in args.items():
 | 
				
			||||||
            if conv_keys:
 | 
					            if conv_keys:
 | 
				
			||||||
                qmp_args[key.replace('_', '-')] = value
 | 
					                qmp_args[key.replace("_", "-")] = value
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                qmp_args[key] = value
 | 
					                qmp_args[key] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -427,7 +456,9 @@ class QEMUMachine(object):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            for key in match:
 | 
					            for key in match:
 | 
				
			||||||
                if key in event:
 | 
					                if key in event:
 | 
				
			||||||
                    if not QEMUMachine.event_match(event[key], match[key]):
 | 
					                    if not QEMUMachine.event_match(
 | 
				
			||||||
 | 
					                        event[key], match[key]
 | 
				
			||||||
 | 
					                    ):
 | 
				
			||||||
                        return False
 | 
					                        return False
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    return False
 | 
					                    return False
 | 
				
			||||||
| 
						 | 
					@ -458,8 +489,9 @@ class QEMUMachine(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def _match(event):
 | 
					        def _match(event):
 | 
				
			||||||
            for name, match in events:
 | 
					            for name, match in events:
 | 
				
			||||||
                if (event['event'] == name and
 | 
					                if event["event"] == name and self.event_match(
 | 
				
			||||||
                        self.event_match(event, match)):
 | 
					                    event, match
 | 
				
			||||||
 | 
					                ):
 | 
				
			||||||
                    return True
 | 
					                    return True
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -531,7 +563,8 @@ class QEMUMachine(object):
 | 
				
			||||||
        Returns a socket connected to the console
 | 
					        Returns a socket connected to the console
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if self._console_socket is None:
 | 
					        if self._console_socket is None:
 | 
				
			||||||
            self._console_socket = socket.socket(socket.AF_UNIX,
 | 
					            self._console_socket = socket.socket(
 | 
				
			||||||
                                                 socket.SOCK_STREAM)
 | 
					                socket.AF_UNIX, socket.SOCK_STREAM
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            self._console_socket.connect(self._console_address)
 | 
					            self._console_socket.connect(self._console_address)
 | 
				
			||||||
        return self._console_socket
 | 
					        return self._console_socket
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,7 +32,7 @@ class QMPTimeoutError(QMPError):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class QEMUMonitorProtocol(object):
 | 
					class QEMUMonitorProtocol(object):
 | 
				
			||||||
    #: Logger object for debugging messages
 | 
					    #: Logger object for debugging messages
 | 
				
			||||||
    logger = logging.getLogger('QMP')
 | 
					    logger = logging.getLogger("QMP")
 | 
				
			||||||
    #: Socket's error class
 | 
					    #: Socket's error class
 | 
				
			||||||
    error = socket.error
 | 
					    error = socket.error
 | 
				
			||||||
    #: Socket's timeout
 | 
					    #: Socket's timeout
 | 
				
			||||||
| 
						 | 
					@ -55,7 +55,9 @@ class QEMUMonitorProtocol(object):
 | 
				
			||||||
        self.__sock = self.__get_sock()
 | 
					        self.__sock = self.__get_sock()
 | 
				
			||||||
        self.__sockfile = None
 | 
					        self.__sockfile = None
 | 
				
			||||||
        if server:
 | 
					        if server:
 | 
				
			||||||
            self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 | 
					            self.__sock.setsockopt(
 | 
				
			||||||
 | 
					                socket.SOL_SOCKET, socket.SO_REUSEADDR, 1
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            self.__sock.bind(self.__address)
 | 
					            self.__sock.bind(self.__address)
 | 
				
			||||||
            self.__sock.listen(1)
 | 
					            self.__sock.listen(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,7 +73,7 @@ class QEMUMonitorProtocol(object):
 | 
				
			||||||
        if greeting is None or "QMP" not in greeting:
 | 
					        if greeting is None or "QMP" not in greeting:
 | 
				
			||||||
            raise QMPConnectError
 | 
					            raise QMPConnectError
 | 
				
			||||||
        # Greeting seems ok, negotiate capabilities
 | 
					        # Greeting seems ok, negotiate capabilities
 | 
				
			||||||
        resp = self.cmd('qmp_capabilities')
 | 
					        resp = self.cmd("qmp_capabilities")
 | 
				
			||||||
        if "return" in resp:
 | 
					        if "return" in resp:
 | 
				
			||||||
            return greeting
 | 
					            return greeting
 | 
				
			||||||
        raise QMPCapabilitiesError
 | 
					        raise QMPCapabilitiesError
 | 
				
			||||||
| 
						 | 
					@ -82,7 +84,7 @@ class QEMUMonitorProtocol(object):
 | 
				
			||||||
            if not data:
 | 
					            if not data:
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            resp = json.loads(data)
 | 
					            resp = json.loads(data)
 | 
				
			||||||
            if 'event' in resp:
 | 
					            if "event" in resp:
 | 
				
			||||||
                self.logger.debug("<<< %s", resp)
 | 
					                self.logger.debug("<<< %s", resp)
 | 
				
			||||||
                self.__events.append(resp)
 | 
					                self.__events.append(resp)
 | 
				
			||||||
                if not only_event:
 | 
					                if not only_event:
 | 
				
			||||||
| 
						 | 
					@ -165,7 +167,7 @@ class QEMUMonitorProtocol(object):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.logger.debug(">>> %s", qmp_cmd)
 | 
					        self.logger.debug(">>> %s", qmp_cmd)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            self.__sock.sendall(json.dumps(qmp_cmd).encode('utf-8'))
 | 
					            self.__sock.sendall(json.dumps(qmp_cmd).encode("utf-8"))
 | 
				
			||||||
        except socket.error as err:
 | 
					        except socket.error as err:
 | 
				
			||||||
            if err[0] == errno.EPIPE:
 | 
					            if err[0] == errno.EPIPE:
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
| 
						 | 
					@ -182,11 +184,11 @@ class QEMUMonitorProtocol(object):
 | 
				
			||||||
        @param args: command arguments (dict)
 | 
					        @param args: command arguments (dict)
 | 
				
			||||||
        @param cmd_id: command id (dict, list, string or int)
 | 
					        @param cmd_id: command id (dict, list, string or int)
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        qmp_cmd = {'execute': name}
 | 
					        qmp_cmd = {"execute": name}
 | 
				
			||||||
        if args:
 | 
					        if args:
 | 
				
			||||||
            qmp_cmd['arguments'] = args
 | 
					            qmp_cmd["arguments"] = args
 | 
				
			||||||
        if cmd_id:
 | 
					        if cmd_id:
 | 
				
			||||||
            qmp_cmd['id'] = cmd_id
 | 
					            qmp_cmd["id"] = cmd_id
 | 
				
			||||||
        return self.cmd_obj(qmp_cmd)
 | 
					        return self.cmd_obj(qmp_cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def command(self, cmd, **kwds):
 | 
					    def command(self, cmd, **kwds):
 | 
				
			||||||
| 
						 | 
					@ -195,8 +197,8 @@ class QEMUMonitorProtocol(object):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        ret = self.cmd(cmd, kwds)
 | 
					        ret = self.cmd(cmd, kwds)
 | 
				
			||||||
        if "error" in ret:
 | 
					        if "error" in ret:
 | 
				
			||||||
            raise Exception(ret['error']['desc'])
 | 
					            raise Exception(ret["error"]["desc"])
 | 
				
			||||||
        return ret['return']
 | 
					        return ret["return"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def pull_event(self, wait=False):
 | 
					    def pull_event(self, wait=False):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,10 +23,6 @@ from ucloud.vmm import VMM
 | 
				
			||||||
from marshmallow import ValidationError
 | 
					from marshmallow import ValidationError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def maintenance():
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class VM:
 | 
					class VM:
 | 
				
			||||||
    def __init__(self, vm_entry):
 | 
					    def __init__(self, vm_entry):
 | 
				
			||||||
        self.schema = VMSchema()
 | 
					        self.schema = VMSchema()
 | 
				
			||||||
| 
						 | 
					@ -35,23 +31,30 @@ class VM:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            self.vm = self.schema.loads(vm_entry.value)
 | 
					            self.vm = self.schema.loads(vm_entry.value)
 | 
				
			||||||
        except ValidationError:
 | 
					        except ValidationError:
 | 
				
			||||||
            logger.exception('Couldn\'t validate VM Entry', vm_entry.value)
 | 
					            logger.exception(
 | 
				
			||||||
 | 
					                "Couldn't validate VM Entry", vm_entry.value
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            self.vm = None
 | 
					            self.vm = None
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.uuid = vm_entry.key.split('/')[-1]
 | 
					            self.uuid = vm_entry.key.split("/")[-1]
 | 
				
			||||||
            self.host_key = self.vm['hostname']
 | 
					            self.host_key = self.vm["hostname"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_qemu_args(self):
 | 
					    def get_qemu_args(self):
 | 
				
			||||||
        command = (
 | 
					        command = (
 | 
				
			||||||
            '-name {owner}_{name}'
 | 
					            "-name {owner}_{name}"
 | 
				
			||||||
            ' -drive file={file},format=raw,if=virtio,cache=none'
 | 
					            " -drive file={file},format=raw,if=virtio,cache=none"
 | 
				
			||||||
            ' -device virtio-rng-pci'
 | 
					            " -device virtio-rng-pci"
 | 
				
			||||||
            ' -m {memory} -smp cores={cores},threads={threads}'
 | 
					            " -m {memory} -smp cores={cores},threads={threads}"
 | 
				
			||||||
        ).format(owner=self.vm['owner'], name=self.vm['name'],
 | 
					        ).format(
 | 
				
			||||||
                 memory=int(self.vm['specs']['ram'].to_MB()), cores=self.vm['specs']['cpu'],
 | 
					            owner=self.vm["owner"],
 | 
				
			||||||
                 threads=1, file=shared.storage_handler.qemu_path_string(self.uuid))
 | 
					            name=self.vm["name"],
 | 
				
			||||||
 | 
					            memory=int(self.vm["specs"]["ram"].to_MB()),
 | 
				
			||||||
 | 
					            cores=self.vm["specs"]["cpu"],
 | 
				
			||||||
 | 
					            threads=1,
 | 
				
			||||||
 | 
					            file=shared.storage_handler.qemu_path_string(self.uuid),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return command.split(' ')
 | 
					        return command.split(" ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def start(self, destination_host_key=None):
 | 
					    def start(self, destination_host_key=None):
 | 
				
			||||||
        migration = False
 | 
					        migration = False
 | 
				
			||||||
| 
						 | 
					@ -63,24 +66,34 @@ class VM:
 | 
				
			||||||
            network_args = self.create_network_dev()
 | 
					            network_args = self.create_network_dev()
 | 
				
			||||||
        except Exception as err:
 | 
					        except Exception as err:
 | 
				
			||||||
            declare_stopped(self.vm)
 | 
					            declare_stopped(self.vm)
 | 
				
			||||||
            self.vm['log'].append('Cannot Setup Network Properly')
 | 
					            self.vm["log"].append("Cannot Setup Network Properly")
 | 
				
			||||||
            logger.error('Cannot Setup Network Properly for vm %s', self.uuid, exc_info=err)
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Cannot Setup Network Properly for vm %s",
 | 
				
			||||||
 | 
					                self.uuid,
 | 
				
			||||||
 | 
					                exc_info=err,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.vmm.start(uuid=self.uuid, migration=migration,
 | 
					            self.vmm.start(
 | 
				
			||||||
                           *self.get_qemu_args(), *network_args)
 | 
					                uuid=self.uuid,
 | 
				
			||||||
 | 
					                migration=migration,
 | 
				
			||||||
 | 
					                *self.get_qemu_args(),
 | 
				
			||||||
 | 
					                *network_args
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            status = self.vmm.get_status(self.uuid)
 | 
					            status = self.vmm.get_status(self.uuid)
 | 
				
			||||||
            if status == 'running':
 | 
					            if status == "running":
 | 
				
			||||||
                self.vm['status'] = VMStatus.running
 | 
					                self.vm["status"] = VMStatus.running
 | 
				
			||||||
                self.vm['vnc_socket'] = self.vmm.get_vnc(self.uuid)
 | 
					                self.vm["vnc_socket"] = self.vmm.get_vnc(self.uuid)
 | 
				
			||||||
            elif status == 'inmigrate':
 | 
					            elif status == "inmigrate":
 | 
				
			||||||
                r = RequestEntry.from_scratch(
 | 
					                r = RequestEntry.from_scratch(
 | 
				
			||||||
                    type=RequestType.TransferVM,  # Transfer VM
 | 
					                    type=RequestType.TransferVM,  # Transfer VM
 | 
				
			||||||
                    hostname=self.host_key,  # Which VM should get this request. It is source host
 | 
					                    hostname=self.host_key,  # Which VM should get this request. It is source host
 | 
				
			||||||
                    uuid=self.uuid,  # uuid of VM
 | 
					                    uuid=self.uuid,  # uuid of VM
 | 
				
			||||||
                    destination_sock_path=join_path(self.vmm.socket_dir, self.uuid),
 | 
					                    destination_sock_path=join_path(
 | 
				
			||||||
 | 
					                        self.vmm.socket_dir, self.uuid
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
                    destination_host_key=destination_host_key,  # Where source host transfer VM
 | 
					                    destination_host_key=destination_host_key,  # Where source host transfer VM
 | 
				
			||||||
                    request_prefix=settings['etcd']['request_prefix']
 | 
					                    request_prefix=settings["etcd"]["request_prefix"],
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                shared.request_pool.put(r)
 | 
					                shared.request_pool.put(r)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
| 
						 | 
					@ -96,15 +109,22 @@ class VM:
 | 
				
			||||||
        self.sync()
 | 
					        self.sync()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def migrate(self, destination_host, destination_sock_path):
 | 
					    def migrate(self, destination_host, destination_sock_path):
 | 
				
			||||||
        self.vmm.transfer(src_uuid=self.uuid, destination_sock_path=destination_sock_path,
 | 
					        self.vmm.transfer(
 | 
				
			||||||
                          host=destination_host)
 | 
					            src_uuid=self.uuid,
 | 
				
			||||||
 | 
					            destination_sock_path=destination_sock_path,
 | 
				
			||||||
 | 
					            host=destination_host,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_network_dev(self):
 | 
					    def create_network_dev(self):
 | 
				
			||||||
        command = ''
 | 
					        command = ""
 | 
				
			||||||
        for network_mac_and_tap in self.vm['network']:
 | 
					        for network_mac_and_tap in self.vm["network"]:
 | 
				
			||||||
            network_name, mac, tap = network_mac_and_tap
 | 
					            network_name, mac, tap = network_mac_and_tap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _key = os.path.join(settings['etcd']['network_prefix'], self.vm['owner'], network_name)
 | 
					            _key = os.path.join(
 | 
				
			||||||
 | 
					                settings["etcd"]["network_prefix"],
 | 
				
			||||||
 | 
					                self.vm["owner"],
 | 
				
			||||||
 | 
					                network_name,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            network = shared.etcd_client.get(_key, value_in_json=True)
 | 
					            network = shared.etcd_client.get(_key, value_in_json=True)
 | 
				
			||||||
            network_schema = NetworkSchema()
 | 
					            network_schema = NetworkSchema()
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
| 
						 | 
					@ -112,49 +132,64 @@ class VM:
 | 
				
			||||||
            except ValidationError:
 | 
					            except ValidationError:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if network['type'] == "vxlan":
 | 
					            if network["type"] == "vxlan":
 | 
				
			||||||
                tap = create_vxlan_br_tap(_id=network['id'],
 | 
					                tap = create_vxlan_br_tap(
 | 
				
			||||||
                                          _dev=settings['network']['vxlan_phy_dev'],
 | 
					                    _id=network["id"],
 | 
				
			||||||
 | 
					                    _dev=settings["network"]["vxlan_phy_dev"],
 | 
				
			||||||
                    tap_id=tap,
 | 
					                    tap_id=tap,
 | 
				
			||||||
                                          ip=network['ipv6'])
 | 
					                    ip=network["ipv6"],
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                all_networks = shared.etcd_client.get_prefix(settings['etcd']['network_prefix'],
 | 
					                all_networks = shared.etcd_client.get_prefix(
 | 
				
			||||||
                                                             value_in_json=True)
 | 
					                    settings["etcd"]["network_prefix"],
 | 
				
			||||||
 | 
					                    value_in_json=True,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if ipaddress.ip_network(network['ipv6']).is_global:
 | 
					                if ipaddress.ip_network(network["ipv6"]).is_global:
 | 
				
			||||||
                    update_radvd_conf(all_networks)
 | 
					                    update_radvd_conf(all_networks)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            command += '-netdev tap,id=vmnet{net_id},ifname={tap},script=no,downscript=no' \
 | 
					            command += (
 | 
				
			||||||
                       ' -device virtio-net-pci,netdev=vmnet{net_id},mac={mac}' \
 | 
					                "-netdev tap,id=vmnet{net_id},ifname={tap},script=no,downscript=no"
 | 
				
			||||||
                       .format(tap=tap, net_id=network['id'], mac=mac)
 | 
					                " -device virtio-net-pci,netdev=vmnet{net_id},mac={mac}".format(
 | 
				
			||||||
 | 
					                    tap=tap, net_id=network["id"], mac=mac
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return command.split(' ')
 | 
					        return command.split(" ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_network_dev(self):
 | 
					    def delete_network_dev(self):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            for network in self.vm['network']:
 | 
					            for network in self.vm["network"]:
 | 
				
			||||||
                network_name = network[0]
 | 
					                network_name = network[0]
 | 
				
			||||||
                _ = network[1]  # tap_mac
 | 
					                _ = network[1]  # tap_mac
 | 
				
			||||||
                tap_id = network[2]
 | 
					                tap_id = network[2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                delete_network_interface('tap{}'.format(tap_id))
 | 
					                delete_network_interface("tap{}".format(tap_id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                owners_vms = shared.vm_pool.by_owner(self.vm['owner'])
 | 
					                owners_vms = shared.vm_pool.by_owner(self.vm["owner"])
 | 
				
			||||||
                owners_running_vms = shared.vm_pool.by_status(VMStatus.running,
 | 
					                owners_running_vms = shared.vm_pool.by_status(
 | 
				
			||||||
                                                              _vms=owners_vms)
 | 
					                    VMStatus.running, _vms=owners_vms
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                networks = map(
 | 
					                networks = map(
 | 
				
			||||||
                    lambda n: n[0], map(lambda vm: vm.network, owners_running_vms)
 | 
					                    lambda n: n[0],
 | 
				
			||||||
 | 
					                    map(lambda vm: vm.network, owners_running_vms),
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                networks_in_use_by_user_vms = [vm[0] for vm in networks]
 | 
					                networks_in_use_by_user_vms = [vm[0] for vm in networks]
 | 
				
			||||||
                if network_name not in networks_in_use_by_user_vms:
 | 
					                if network_name not in networks_in_use_by_user_vms:
 | 
				
			||||||
                    network_entry = resolve_network(network[0], self.vm['owner'])
 | 
					                    network_entry = resolve_network(
 | 
				
			||||||
 | 
					                        network[0], self.vm["owner"]
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    if network_entry:
 | 
					                    if network_entry:
 | 
				
			||||||
                        network_type = network_entry.value["type"]
 | 
					                        network_type = network_entry.value["type"]
 | 
				
			||||||
                        network_id = network_entry.value["id"]
 | 
					                        network_id = network_entry.value["id"]
 | 
				
			||||||
                        if network_type == "vxlan":
 | 
					                        if network_type == "vxlan":
 | 
				
			||||||
                            delete_network_interface('br{}'.format(network_id))
 | 
					                            delete_network_interface(
 | 
				
			||||||
                            delete_network_interface('vxlan{}'.format(network_id))
 | 
					                                "br{}".format(network_id)
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                            delete_network_interface(
 | 
				
			||||||
 | 
					                                "vxlan{}".format(network_id)
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            logger.exception("Exception in network interface deletion")
 | 
					            logger.exception("Exception in network interface deletion")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -163,15 +198,21 @@ class VM:
 | 
				
			||||||
            # File Already exists. No Problem Continue
 | 
					            # File Already exists. No Problem Continue
 | 
				
			||||||
            logger.debug("Image for vm %s exists", self.uuid)
 | 
					            logger.debug("Image for vm %s exists", self.uuid)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            if shared.storage_handler.make_vm_image(src=self.vm['image_uuid'], dest=self.uuid):
 | 
					            if shared.storage_handler.make_vm_image(
 | 
				
			||||||
                if not shared.storage_handler.resize_vm_image(path=self.uuid,
 | 
					                src=self.vm["image_uuid"], dest=self.uuid
 | 
				
			||||||
                                                              size=int(self.vm['specs']['os-ssd'].to_MB())):
 | 
					            ):
 | 
				
			||||||
                    self.vm['status'] = VMStatus.error
 | 
					                if not shared.storage_handler.resize_vm_image(
 | 
				
			||||||
 | 
					                    path=self.uuid,
 | 
				
			||||||
 | 
					                    size=int(self.vm["specs"]["os-ssd"].to_MB()),
 | 
				
			||||||
 | 
					                ):
 | 
				
			||||||
 | 
					                    self.vm["status"] = VMStatus.error
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    logger.info("New VM Created")
 | 
					                    logger.info("New VM Created")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def sync(self):
 | 
					    def sync(self):
 | 
				
			||||||
        shared.etcd_client.put(self.key, self.schema.dump(self.vm), value_in_json=True)
 | 
					        shared.etcd_client.put(
 | 
				
			||||||
 | 
					            self.key, self.schema.dump(self.vm), value_in_json=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete(self):
 | 
					    def delete(self):
 | 
				
			||||||
        self.stop()
 | 
					        self.stop()
 | 
				
			||||||
| 
						 | 
					@ -186,50 +227,77 @@ class VM:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def resolve_network(network_name, network_owner):
 | 
					def resolve_network(network_name, network_owner):
 | 
				
			||||||
    network = shared.etcd_client.get(
 | 
					    network = shared.etcd_client.get(
 | 
				
			||||||
        join_path(settings['etcd']['network_prefix'], network_owner, network_name), value_in_json=True
 | 
					        join_path(
 | 
				
			||||||
 | 
					            settings["etcd"]["network_prefix"],
 | 
				
			||||||
 | 
					            network_owner,
 | 
				
			||||||
 | 
					            network_name,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        value_in_json=True,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    return network
 | 
					    return network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_vxlan_br_tap(_id, _dev, tap_id, ip=None):
 | 
					def create_vxlan_br_tap(_id, _dev, tap_id, ip=None):
 | 
				
			||||||
    network_script_base = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'network')
 | 
					    network_script_base = os.path.join(
 | 
				
			||||||
    vxlan = create_dev(script=os.path.join(network_script_base, 'create-vxlan.sh'),
 | 
					        os.path.dirname(os.path.dirname(__file__)), "network"
 | 
				
			||||||
                       _id=_id, dev=_dev)
 | 
					    )
 | 
				
			||||||
 | 
					    vxlan = create_dev(
 | 
				
			||||||
 | 
					        script=os.path.join(network_script_base, "create-vxlan.sh"),
 | 
				
			||||||
 | 
					        _id=_id,
 | 
				
			||||||
 | 
					        dev=_dev,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    if vxlan:
 | 
					    if vxlan:
 | 
				
			||||||
        bridge = create_dev(script=os.path.join(network_script_base, 'create-bridge.sh'),
 | 
					        bridge = create_dev(
 | 
				
			||||||
                            _id=_id, dev=vxlan, ip=ip)
 | 
					            script=os.path.join(
 | 
				
			||||||
 | 
					                network_script_base, "create-bridge.sh"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            _id=_id,
 | 
				
			||||||
 | 
					            dev=vxlan,
 | 
				
			||||||
 | 
					            ip=ip,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        if bridge:
 | 
					        if bridge:
 | 
				
			||||||
            tap = create_dev(script=os.path.join(network_script_base, 'create-tap.sh'),
 | 
					            tap = create_dev(
 | 
				
			||||||
                             _id=str(tap_id), dev=bridge)
 | 
					                script=os.path.join(
 | 
				
			||||||
 | 
					                    network_script_base, "create-tap.sh"
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                _id=str(tap_id),
 | 
				
			||||||
 | 
					                dev=bridge,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            if tap:
 | 
					            if tap:
 | 
				
			||||||
                return tap
 | 
					                return tap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def update_radvd_conf(all_networks):
 | 
					def update_radvd_conf(all_networks):
 | 
				
			||||||
    network_script_base = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'network')
 | 
					    network_script_base = os.path.join(
 | 
				
			||||||
 | 
					        os.path.dirname(os.path.dirname(__file__)), "network"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    networks = {
 | 
					    networks = {
 | 
				
			||||||
        net.value['ipv6']: net.value['id']
 | 
					        net.value["ipv6"]: net.value["id"]
 | 
				
			||||||
        for net in all_networks
 | 
					        for net in all_networks
 | 
				
			||||||
        if net.value.get('ipv6') and ipaddress.ip_network(net.value.get('ipv6')).is_global
 | 
					        if net.value.get("ipv6")
 | 
				
			||||||
 | 
					        and ipaddress.ip_network(net.value.get("ipv6")).is_global
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    radvd_template = open(os.path.join(network_script_base,
 | 
					    radvd_template = open(
 | 
				
			||||||
                                       'radvd-template.conf'), 'r').read()
 | 
					        os.path.join(network_script_base, "radvd-template.conf"), "r"
 | 
				
			||||||
 | 
					    ).read()
 | 
				
			||||||
    radvd_template = Template(radvd_template)
 | 
					    radvd_template = Template(radvd_template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    content = [
 | 
					    content = [
 | 
				
			||||||
        radvd_template.safe_substitute(
 | 
					        radvd_template.safe_substitute(
 | 
				
			||||||
            bridge='br{}'.format(networks[net]),
 | 
					            bridge="br{}".format(networks[net]), prefix=net
 | 
				
			||||||
            prefix=net
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        for net in networks if networks.get(net)
 | 
					        for net in networks
 | 
				
			||||||
 | 
					        if networks.get(net)
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    with open('/etc/radvd.conf', 'w') as radvd_conf:
 | 
					    with open("/etc/radvd.conf", "w") as radvd_conf:
 | 
				
			||||||
        radvd_conf.writelines(content)
 | 
					        radvd_conf.writelines(content)
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        sp.check_output(['systemctl', 'restart', 'radvd'])
 | 
					        sp.check_output(["systemctl", "restart", "radvd"])
 | 
				
			||||||
    except sp.CalledProcessError:
 | 
					    except sp.CalledProcessError:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            sp.check_output(['service', 'radvd', 'restart'])
 | 
					            sp.check_output(["service", "radvd", "restart"])
 | 
				
			||||||
        except sp.CalledProcessError as err:
 | 
					        except sp.CalledProcessError as err:
 | 
				
			||||||
            raise err.__class__('Cannot start/restart radvd service', err.cmd) from err
 | 
					            raise err.__class__(
 | 
				
			||||||
 | 
					                "Cannot start/restart radvd service", err.cmd
 | 
				
			||||||
 | 
					            ) from err
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,13 @@ from ucloud.imagescanner import logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def qemu_img_type(path):
 | 
					def qemu_img_type(path):
 | 
				
			||||||
    qemu_img_info_command = ["qemu-img", "info", "--output", "json", path]
 | 
					    qemu_img_info_command = [
 | 
				
			||||||
 | 
					        "qemu-img",
 | 
				
			||||||
 | 
					        "info",
 | 
				
			||||||
 | 
					        "--output",
 | 
				
			||||||
 | 
					        "json",
 | 
				
			||||||
 | 
					        path,
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        qemu_img_info = sp.check_output(qemu_img_info_command)
 | 
					        qemu_img_info = sp.check_output(qemu_img_info_command)
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
| 
						 | 
					@ -22,32 +28,57 @@ def qemu_img_type(path):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
    # We want to get images entries that requests images to be created
 | 
					    # We want to get images entries that requests images to be created
 | 
				
			||||||
    images = shared.etcd_client.get_prefix(settings['etcd']['image_prefix'], value_in_json=True)
 | 
					    images = shared.etcd_client.get_prefix(
 | 
				
			||||||
    images_to_be_created = list(filter(lambda im: im.value['status'] == 'TO_BE_CREATED', images))
 | 
					        settings["etcd"]["image_prefix"], value_in_json=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    images_to_be_created = list(
 | 
				
			||||||
 | 
					        filter(lambda im: im.value["status"] == "TO_BE_CREATED", images)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for image in images_to_be_created:
 | 
					    for image in images_to_be_created:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            image_uuid = image.key.split('/')[-1]
 | 
					            image_uuid = image.key.split("/")[-1]
 | 
				
			||||||
            image_owner = image.value['owner']
 | 
					            image_owner = image.value["owner"]
 | 
				
			||||||
            image_filename = image.value['filename']
 | 
					            image_filename = image.value["filename"]
 | 
				
			||||||
            image_store_name = image.value['store_name']
 | 
					            image_store_name = image.value["store_name"]
 | 
				
			||||||
            image_full_path = join_path(settings['storage']['file_dir'], image_owner, image_filename)
 | 
					            image_full_path = join_path(
 | 
				
			||||||
 | 
					                settings["storage"]["file_dir"],
 | 
				
			||||||
 | 
					                image_owner,
 | 
				
			||||||
 | 
					                image_filename,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            image_stores = shared.etcd_client.get_prefix(settings['etcd']['image_store_prefix'],
 | 
					            image_stores = shared.etcd_client.get_prefix(
 | 
				
			||||||
                                                  value_in_json=True)
 | 
					                settings["etcd"]["image_store_prefix"],
 | 
				
			||||||
            user_image_store = next(filter(
 | 
					                value_in_json=True,
 | 
				
			||||||
                lambda s, store_name=image_store_name: s.value["name"] == store_name,
 | 
					            )
 | 
				
			||||||
                image_stores
 | 
					            user_image_store = next(
 | 
				
			||||||
            ))
 | 
					                filter(
 | 
				
			||||||
 | 
					                    lambda s, store_name=image_store_name: s.value[
 | 
				
			||||||
 | 
					                        "name"
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                    == store_name,
 | 
				
			||||||
 | 
					                    image_stores,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            image_store_pool = user_image_store.value['attributes']['pool']
 | 
					            image_store_pool = user_image_store.value["attributes"][
 | 
				
			||||||
 | 
					                "pool"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            logger.exception(e)
 | 
					            logger.exception(e)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # At least our basic data is available
 | 
					            # At least our basic data is available
 | 
				
			||||||
            qemu_img_convert_command = ["qemu-img", "convert", "-f", "qcow2",
 | 
					            qemu_img_convert_command = [
 | 
				
			||||||
                                        "-O", "raw", image_full_path, "image.raw"]
 | 
					                "qemu-img",
 | 
				
			||||||
 | 
					                "convert",
 | 
				
			||||||
 | 
					                "-f",
 | 
				
			||||||
 | 
					                "qcow2",
 | 
				
			||||||
 | 
					                "-O",
 | 
				
			||||||
 | 
					                "raw",
 | 
				
			||||||
 | 
					                image_full_path,
 | 
				
			||||||
 | 
					                "image.raw",
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if qemu_img_type(image_full_path) == "qcow2":
 | 
					            if qemu_img_type(image_full_path) == "qcow2":
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
| 
						 | 
					@ -55,16 +86,20 @@ def main():
 | 
				
			||||||
                    sp.check_output(qemu_img_convert_command,)
 | 
					                    sp.check_output(qemu_img_convert_command,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                except sp.CalledProcessError:
 | 
					                except sp.CalledProcessError:
 | 
				
			||||||
                    logger.exception('Image convertion from .qcow2 to .raw failed.')
 | 
					                    logger.exception(
 | 
				
			||||||
 | 
					                        "Image convertion from .qcow2 to .raw failed."
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    # Import and Protect
 | 
					                    # Import and Protect
 | 
				
			||||||
                    r_status = shared.storage_handler.import_image(src="image.raw",
 | 
					                    r_status = shared.storage_handler.import_image(
 | 
				
			||||||
                                                                   dest=image_uuid,
 | 
					                        src="image.raw", dest=image_uuid, protect=True
 | 
				
			||||||
                                                                   protect=True)
 | 
					                    )
 | 
				
			||||||
                    if r_status:
 | 
					                    if r_status:
 | 
				
			||||||
                        # Everything is successfully done
 | 
					                        # Everything is successfully done
 | 
				
			||||||
                        image.value["status"] = "CREATED"
 | 
					                        image.value["status"] = "CREATED"
 | 
				
			||||||
                        shared.etcd_client.put(image.key, json.dumps(image.value))
 | 
					                        shared.etcd_client.put(
 | 
				
			||||||
 | 
					                            image.key, json.dumps(image.value)
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
                finally:
 | 
					                finally:
 | 
				
			||||||
                    try:
 | 
					                    try:
 | 
				
			||||||
                        os.remove("image.raw")
 | 
					                        os.remove("image.raw")
 | 
				
			||||||
| 
						 | 
					@ -74,7 +109,9 @@ def main():
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                # The user provided image is either not found or of invalid format
 | 
					                # The user provided image is either not found or of invalid format
 | 
				
			||||||
                image.value["status"] = "INVALID_IMAGE"
 | 
					                image.value["status"] = "INVALID_IMAGE"
 | 
				
			||||||
                shared.etcd_client.put(image.key, json.dumps(image.value))
 | 
					                shared.etcd_client.put(
 | 
				
			||||||
 | 
					                    image.key, json.dumps(image.value)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,33 +21,39 @@ def handle_exception(e):
 | 
				
			||||||
        return e
 | 
					        return e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # now you're handling non-HTTP exceptions only
 | 
					    # now you're handling non-HTTP exceptions only
 | 
				
			||||||
    return {'message': 'Server Error'}, 500
 | 
					    return {"message": "Server Error"}, 500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_vm_entry(mac_addr):
 | 
					def get_vm_entry(mac_addr):
 | 
				
			||||||
    return next(filter(lambda vm: mac_addr in list(zip(*vm.network))[1], shared.vm_pool.vms), None)
 | 
					    return next(
 | 
				
			||||||
 | 
					        filter(
 | 
				
			||||||
 | 
					            lambda vm: mac_addr in list(zip(*vm.network))[1],
 | 
				
			||||||
 | 
					            shared.vm_pool.vms,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# https://stackoverflow.com/questions/37140846/how-to-convert-ipv6-link-local-address-to-mac-address-in-python
 | 
					# https://stackoverflow.com/questions/37140846/how-to-convert-ipv6-link-local-address-to-mac-address-in-python
 | 
				
			||||||
def ipv62mac(ipv6):
 | 
					def ipv62mac(ipv6):
 | 
				
			||||||
    # remove subnet info if given
 | 
					    # remove subnet info if given
 | 
				
			||||||
    subnet_index = ipv6.find('/')
 | 
					    subnet_index = ipv6.find("/")
 | 
				
			||||||
    if subnet_index != -1:
 | 
					    if subnet_index != -1:
 | 
				
			||||||
        ipv6 = ipv6[:subnet_index]
 | 
					        ipv6 = ipv6[:subnet_index]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipv6_parts = ipv6.split(':')
 | 
					    ipv6_parts = ipv6.split(":")
 | 
				
			||||||
    mac_parts = list()
 | 
					    mac_parts = list()
 | 
				
			||||||
    for ipv6_part in ipv6_parts[-4:]:
 | 
					    for ipv6_part in ipv6_parts[-4:]:
 | 
				
			||||||
        while len(ipv6_part) < 4:
 | 
					        while len(ipv6_part) < 4:
 | 
				
			||||||
            ipv6_part = '0' + ipv6_part
 | 
					            ipv6_part = "0" + ipv6_part
 | 
				
			||||||
        mac_parts.append(ipv6_part[:2])
 | 
					        mac_parts.append(ipv6_part[:2])
 | 
				
			||||||
        mac_parts.append(ipv6_part[-2:])
 | 
					        mac_parts.append(ipv6_part[-2:])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # modify parts to match MAC value
 | 
					    # modify parts to match MAC value
 | 
				
			||||||
    mac_parts[0] = '%02x' % (int(mac_parts[0], 16) ^ 2)
 | 
					    mac_parts[0] = "%02x" % (int(mac_parts[0], 16) ^ 2)
 | 
				
			||||||
    del mac_parts[4]
 | 
					    del mac_parts[4]
 | 
				
			||||||
    del mac_parts[3]
 | 
					    del mac_parts[3]
 | 
				
			||||||
    return ':'.join(mac_parts)
 | 
					    return ":".join(mac_parts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Root(Resource):
 | 
					class Root(Resource):
 | 
				
			||||||
| 
						 | 
					@ -56,19 +62,27 @@ class Root(Resource):
 | 
				
			||||||
        data = get_vm_entry(ipv62mac(request.remote_addr))
 | 
					        data = get_vm_entry(ipv62mac(request.remote_addr))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not data:
 | 
					        if not data:
 | 
				
			||||||
            return {'message': 'Metadata for such VM does not exists.'}, 404
 | 
					            return (
 | 
				
			||||||
 | 
					                {"message": "Metadata for such VM does not exists."},
 | 
				
			||||||
 | 
					                404,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            etcd_key = os.path.join(settings['etcd']['user_prefix'],
 | 
					            etcd_key = os.path.join(
 | 
				
			||||||
                                    data.value['owner_realm'],
 | 
					                settings["etcd"]["user_prefix"],
 | 
				
			||||||
                                    data.value['owner'], 'key')
 | 
					                data.value["owner_realm"],
 | 
				
			||||||
            etcd_entry = shared.etcd_client.get_prefix(etcd_key, value_in_json=True)
 | 
					                data.value["owner"],
 | 
				
			||||||
 | 
					                "key",
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            etcd_entry = shared.etcd_client.get_prefix(
 | 
				
			||||||
 | 
					                etcd_key, value_in_json=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            user_personal_ssh_keys = [key.value for key in etcd_entry]
 | 
					            user_personal_ssh_keys = [key.value for key in etcd_entry]
 | 
				
			||||||
            data.value['metadata']['ssh-keys'] += user_personal_ssh_keys
 | 
					            data.value["metadata"]["ssh-keys"] += user_personal_ssh_keys
 | 
				
			||||||
            return data.value['metadata'], 200
 | 
					            return data.value["metadata"], 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def post():
 | 
					    def post():
 | 
				
			||||||
        return {'message': 'Previous Implementation is deprecated.'}
 | 
					        return {"message": "Previous Implementation is deprecated."}
 | 
				
			||||||
        # data = etcd_client.get("/v1/metadata/{}".format(request.remote_addr), value_in_json=True)
 | 
					        # data = etcd_client.get("/v1/metadata/{}".format(request.remote_addr), value_in_json=True)
 | 
				
			||||||
        # print(data)
 | 
					        # print(data)
 | 
				
			||||||
        # if data:
 | 
					        # if data:
 | 
				
			||||||
| 
						 | 
					@ -94,12 +108,12 @@ class Root(Resource):
 | 
				
			||||||
        #                     data, value_in_json=True)
 | 
					        #                     data, value_in_json=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
api.add_resource(Root, '/')
 | 
					api.add_resource(Root, "/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
    app.run(debug=True, host="::", port="80")
 | 
					    app.run(debug=True, host="::", port="80")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    main()
 | 
					    main()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,17 +24,35 @@ def remaining_resources(host_specs, vms_specs):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for component in _vms_specs:
 | 
					    for component in _vms_specs:
 | 
				
			||||||
        if isinstance(_vms_specs[component], str):
 | 
					        if isinstance(_vms_specs[component], str):
 | 
				
			||||||
            _vms_specs[component] = int(bitmath.parse_string_unsafe(_vms_specs[component]).to_MB())
 | 
					            _vms_specs[component] = int(
 | 
				
			||||||
 | 
					                bitmath.parse_string_unsafe(
 | 
				
			||||||
 | 
					                    _vms_specs[component]
 | 
				
			||||||
 | 
					                ).to_MB()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        elif isinstance(_vms_specs[component], list):
 | 
					        elif isinstance(_vms_specs[component], list):
 | 
				
			||||||
            _vms_specs[component] = map(lambda x: int(bitmath.parse_string_unsafe(x).to_MB()), _vms_specs[component])
 | 
					            _vms_specs[component] = map(
 | 
				
			||||||
            _vms_specs[component] = reduce(lambda x, y: x + y, _vms_specs[component], 0)
 | 
					                lambda x: int(bitmath.parse_string_unsafe(x).to_MB()),
 | 
				
			||||||
 | 
					                _vms_specs[component],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            _vms_specs[component] = reduce(
 | 
				
			||||||
 | 
					                lambda x, y: x + y, _vms_specs[component], 0
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for component in _remaining:
 | 
					    for component in _remaining:
 | 
				
			||||||
        if isinstance(_remaining[component], str):
 | 
					        if isinstance(_remaining[component], str):
 | 
				
			||||||
            _remaining[component] = int(bitmath.parse_string_unsafe(_remaining[component]).to_MB())
 | 
					            _remaining[component] = int(
 | 
				
			||||||
 | 
					                bitmath.parse_string_unsafe(
 | 
				
			||||||
 | 
					                    _remaining[component]
 | 
				
			||||||
 | 
					                ).to_MB()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        elif isinstance(_remaining[component], list):
 | 
					        elif isinstance(_remaining[component], list):
 | 
				
			||||||
            _remaining[component] = map(lambda x: int(bitmath.parse_string_unsafe(x).to_MB()), _remaining[component])
 | 
					            _remaining[component] = map(
 | 
				
			||||||
            _remaining[component] = reduce(lambda x, y: x + y, _remaining[component], 0)
 | 
					                lambda x: int(bitmath.parse_string_unsafe(x).to_MB()),
 | 
				
			||||||
 | 
					                _remaining[component],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            _remaining[component] = reduce(
 | 
				
			||||||
 | 
					                lambda x, y: x + y, _remaining[component], 0
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _remaining.subtract(_vms_specs)
 | 
					    _remaining.subtract(_vms_specs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,11 +77,15 @@ def get_suitable_host(vm_specs, hosts=None):
 | 
				
			||||||
        running_vms_specs = [vm.specs for vm in vms]
 | 
					        running_vms_specs = [vm.specs for vm in vms]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Accumulate all of their combined specs
 | 
					        # Accumulate all of their combined specs
 | 
				
			||||||
        running_vms_accumulated_specs = accumulated_specs(running_vms_specs)
 | 
					        running_vms_accumulated_specs = accumulated_specs(
 | 
				
			||||||
 | 
					            running_vms_specs
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Find out remaining resources after
 | 
					        # Find out remaining resources after
 | 
				
			||||||
        # host_specs - already running vm_specs
 | 
					        # host_specs - already running vm_specs
 | 
				
			||||||
        remaining = remaining_resources(host.specs, running_vms_accumulated_specs)
 | 
					        remaining = remaining_resources(
 | 
				
			||||||
 | 
					            host.specs, running_vms_accumulated_specs
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Find out remaining - new_vm_specs
 | 
					        # Find out remaining - new_vm_specs
 | 
				
			||||||
        remaining = remaining_resources(remaining, vm_specs)
 | 
					        remaining = remaining_resources(remaining, vm_specs)
 | 
				
			||||||
| 
						 | 
					@ -95,7 +117,7 @@ def dead_host_mitigation(dead_hosts_keys):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vms_hosted_on_dead_host = shared.vm_pool.by_host(host_key)
 | 
					        vms_hosted_on_dead_host = shared.vm_pool.by_host(host_key)
 | 
				
			||||||
        for vm in vms_hosted_on_dead_host:
 | 
					        for vm in vms_hosted_on_dead_host:
 | 
				
			||||||
            vm.status = 'UNKNOWN'
 | 
					            vm.status = "UNKNOWN"
 | 
				
			||||||
            shared.vm_pool.put(vm)
 | 
					            shared.vm_pool.put(vm)
 | 
				
			||||||
        shared.host_pool.put(host)
 | 
					        shared.host_pool.put(host)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,10 +126,12 @@ def assign_host(vm):
 | 
				
			||||||
    vm.hostname = get_suitable_host(vm.specs)
 | 
					    vm.hostname = get_suitable_host(vm.specs)
 | 
				
			||||||
    shared.vm_pool.put(vm)
 | 
					    shared.vm_pool.put(vm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    r = RequestEntry.from_scratch(type=RequestType.StartVM,
 | 
					    r = RequestEntry.from_scratch(
 | 
				
			||||||
 | 
					        type=RequestType.StartVM,
 | 
				
			||||||
        uuid=vm.uuid,
 | 
					        uuid=vm.uuid,
 | 
				
			||||||
        hostname=vm.hostname,
 | 
					        hostname=vm.hostname,
 | 
				
			||||||
                                  request_prefix=settings['etcd']['request_prefix'])
 | 
					        request_prefix=settings["etcd"]["request_prefix"],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    shared.request_pool.put(r)
 | 
					    shared.request_pool.put(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    vm.log.append("VM scheduled for starting")
 | 
					    vm.log.append("VM scheduled for starting")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,8 +7,13 @@
 | 
				
			||||||
from ucloud.common.request import RequestEntry, RequestType
 | 
					from ucloud.common.request import RequestEntry, RequestType
 | 
				
			||||||
from ucloud.shared import shared
 | 
					from ucloud.shared import shared
 | 
				
			||||||
from ucloud.settings import settings
 | 
					from ucloud.settings import settings
 | 
				
			||||||
from .helper import (get_suitable_host, dead_host_mitigation, dead_host_detection,
 | 
					from .helper import (
 | 
				
			||||||
                     assign_host, NoSuitableHostFound)
 | 
					    get_suitable_host,
 | 
				
			||||||
 | 
					    dead_host_mitigation,
 | 
				
			||||||
 | 
					    dead_host_detection,
 | 
				
			||||||
 | 
					    assign_host,
 | 
				
			||||||
 | 
					    NoSuitableHostFound,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from . import logger
 | 
					from . import logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,8 +21,14 @@ def main():
 | 
				
			||||||
    pending_vms = []
 | 
					    pending_vms = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for request_iterator in [
 | 
					    for request_iterator in [
 | 
				
			||||||
        shared.etcd_client.get_prefix(settings['etcd']['request_prefix'], value_in_json=True),
 | 
					        shared.etcd_client.get_prefix(
 | 
				
			||||||
        shared.etcd_client.watch_prefix(settings['etcd']['request_prefix'], timeout=5, value_in_json=True),
 | 
					            settings["etcd"]["request_prefix"], value_in_json=True
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        shared.etcd_client.watch_prefix(
 | 
				
			||||||
 | 
					            settings["etcd"]["request_prefix"],
 | 
				
			||||||
 | 
					            timeout=5,
 | 
				
			||||||
 | 
					            value_in_json=True,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
    ]:
 | 
					    ]:
 | 
				
			||||||
        for request_event in request_iterator:
 | 
					        for request_event in request_iterator:
 | 
				
			||||||
            request_entry = RequestEntry(request_event)
 | 
					            request_entry = RequestEntry(request_event)
 | 
				
			||||||
| 
						 | 
					@ -41,25 +52,39 @@ def main():
 | 
				
			||||||
                # on our behalf.
 | 
					                # on our behalf.
 | 
				
			||||||
                while pending_vms:
 | 
					                while pending_vms:
 | 
				
			||||||
                    pending_vm_entry = pending_vms.pop()
 | 
					                    pending_vm_entry = pending_vms.pop()
 | 
				
			||||||
                    r = RequestEntry.from_scratch(type="ScheduleVM",
 | 
					                    r = RequestEntry.from_scratch(
 | 
				
			||||||
 | 
					                        type="ScheduleVM",
 | 
				
			||||||
                        uuid=pending_vm_entry.uuid,
 | 
					                        uuid=pending_vm_entry.uuid,
 | 
				
			||||||
                        hostname=pending_vm_entry.hostname,
 | 
					                        hostname=pending_vm_entry.hostname,
 | 
				
			||||||
                                                  request_prefix=settings['etcd']['request_prefix'])
 | 
					                        request_prefix=settings["etcd"][
 | 
				
			||||||
 | 
					                            "request_prefix"
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    shared.request_pool.put(r)
 | 
					                    shared.request_pool.put(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif request_entry.type == RequestType.ScheduleVM:
 | 
					            elif request_entry.type == RequestType.ScheduleVM:
 | 
				
			||||||
                logger.debug("%s, %s", request_entry.key, request_entry.value)
 | 
					                logger.debug(
 | 
				
			||||||
 | 
					                    "%s, %s", request_entry.key, request_entry.value
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                vm_entry = shared.vm_pool.get(request_entry.uuid)
 | 
					                vm_entry = shared.vm_pool.get(request_entry.uuid)
 | 
				
			||||||
                if vm_entry is None:
 | 
					                if vm_entry is None:
 | 
				
			||||||
                    logger.info("Trying to act on {} but it is deleted".format(request_entry.uuid))
 | 
					                    logger.info(
 | 
				
			||||||
 | 
					                        "Trying to act on {} but it is deleted".format(
 | 
				
			||||||
 | 
					                            request_entry.uuid
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
                shared.etcd_client.client.delete(request_entry.key)  # consume Request
 | 
					                shared.etcd_client.client.delete(
 | 
				
			||||||
 | 
					                    request_entry.key
 | 
				
			||||||
 | 
					                )  # consume Request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    assign_host(vm_entry)
 | 
					                    assign_host(vm_entry)
 | 
				
			||||||
                except NoSuitableHostFound:
 | 
					                except NoSuitableHostFound:
 | 
				
			||||||
                    vm_entry.add_log("Can't schedule VM. No Resource Left.")
 | 
					                    vm_entry.add_log(
 | 
				
			||||||
 | 
					                        "Can't schedule VM. No Resource Left."
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    shared.vm_pool.put(vm_entry)
 | 
					                    shared.vm_pool.put(vm_entry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    pending_vms.append(vm_entry)
 | 
					                    pending_vms.append(vm_entry)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,9 +70,15 @@ class TestFunctions(unittest.TestCase):
 | 
				
			||||||
            "last_heartbeat": datetime.utcnow().isoformat(),
 | 
					            "last_heartbeat": datetime.utcnow().isoformat(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        with self.client.client.lock("lock"):
 | 
					        with self.client.client.lock("lock"):
 | 
				
			||||||
            self.client.put(f"{self.host_prefix}/1", host1, value_in_json=True)
 | 
					            self.client.put(
 | 
				
			||||||
            self.client.put(f"{self.host_prefix}/2", host2, value_in_json=True)
 | 
					                f"{self.host_prefix}/1", host1, value_in_json=True
 | 
				
			||||||
            self.client.put(f"{self.host_prefix}/3", host3, value_in_json=True)
 | 
					            )
 | 
				
			||||||
 | 
					            self.client.put(
 | 
				
			||||||
 | 
					                f"{self.host_prefix}/2", host2, value_in_json=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            self.client.put(
 | 
				
			||||||
 | 
					                f"{self.host_prefix}/3", host3, value_in_json=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_vms(self):
 | 
					    def create_vms(self):
 | 
				
			||||||
        vm1 = json.dumps(
 | 
					        vm1 = json.dumps(
 | 
				
			||||||
| 
						 | 
					@ -146,15 +152,17 @@ class TestFunctions(unittest.TestCase):
 | 
				
			||||||
            {"cpu": 8, "ram": 32},
 | 
					            {"cpu": 8, "ram": 32},
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        self.assertEqual(
 | 
					        self.assertEqual(
 | 
				
			||||||
            accumulated_specs(vms), {"ssd": 10, "cpu": 16, "ram": 48, "hdd": 10}
 | 
					            accumulated_specs(vms),
 | 
				
			||||||
 | 
					            {"ssd": 10, "cpu": 16, "ram": 48, "hdd": 10},
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_remaining_resources(self):
 | 
					    def test_remaining_resources(self):
 | 
				
			||||||
        host_specs = {"ssd": 10, "cpu": 16, "ram": 48, "hdd": 10}
 | 
					        host_specs = {"ssd": 10, "cpu": 16, "ram": 48, "hdd": 10}
 | 
				
			||||||
        vms_specs = {"ssd": 10, "cpu": 32, "ram": 12, "hdd": 0}
 | 
					        vms_specs = {"ssd": 10, "cpu": 32, "ram": 12, "hdd": 0}
 | 
				
			||||||
        resultant_specs = {"ssd": 0, "cpu": -16, "ram": 36, "hdd": 10}
 | 
					        resultant_specs = {"ssd": 0, "cpu": -16, "ram": 36, "hdd": 10}
 | 
				
			||||||
        self.assertEqual(remaining_resources(host_specs, vms_specs),
 | 
					        self.assertEqual(
 | 
				
			||||||
                         resultant_specs)
 | 
					            remaining_resources(host_specs, vms_specs), resultant_specs
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_vmpool(self):
 | 
					    def test_vmpool(self):
 | 
				
			||||||
        self.p.join(1)
 | 
					        self.p.join(1)
 | 
				
			||||||
| 
						 | 
					@ -167,7 +175,12 @@ class TestFunctions(unittest.TestCase):
 | 
				
			||||||
                f"{self.vm_prefix}/1",
 | 
					                f"{self.vm_prefix}/1",
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    "owner": "meow",
 | 
					                    "owner": "meow",
 | 
				
			||||||
                    "specs": {"cpu": 4, "ram": 8, "hdd": 100, "sdd": 256},
 | 
					                    "specs": {
 | 
				
			||||||
 | 
					                        "cpu": 4,
 | 
				
			||||||
 | 
					                        "ram": 8,
 | 
				
			||||||
 | 
					                        "hdd": 100,
 | 
				
			||||||
 | 
					                        "sdd": 256,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
                    "hostname": f"{self.host_prefix}/3",
 | 
					                    "hostname": f"{self.host_prefix}/3",
 | 
				
			||||||
                    "status": "SCHEDULED_DEPLOY",
 | 
					                    "status": "SCHEDULED_DEPLOY",
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
| 
						 | 
					@ -182,7 +195,12 @@ class TestFunctions(unittest.TestCase):
 | 
				
			||||||
                f"{self.vm_prefix}/7",
 | 
					                f"{self.vm_prefix}/7",
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    "owner": "meow",
 | 
					                    "owner": "meow",
 | 
				
			||||||
                    "specs": {"cpu": 10, "ram": 22, "hdd": 146, "sdd": 0},
 | 
					                    "specs": {
 | 
				
			||||||
 | 
					                        "cpu": 10,
 | 
				
			||||||
 | 
					                        "ram": 22,
 | 
				
			||||||
 | 
					                        "hdd": 146,
 | 
				
			||||||
 | 
					                        "sdd": 0,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
                    "hostname": "",
 | 
					                    "hostname": "",
 | 
				
			||||||
                    "status": "REQUESTED_NEW",
 | 
					                    "status": "REQUESTED_NEW",
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
| 
						 | 
					@ -197,7 +215,12 @@ class TestFunctions(unittest.TestCase):
 | 
				
			||||||
                f"{self.vm_prefix}/7",
 | 
					                f"{self.vm_prefix}/7",
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    "owner": "meow",
 | 
					                    "owner": "meow",
 | 
				
			||||||
                    "specs": {"cpu": 10, "ram": 22, "hdd": 146, "sdd": 0},
 | 
					                    "specs": {
 | 
				
			||||||
 | 
					                        "cpu": 10,
 | 
				
			||||||
 | 
					                        "ram": 22,
 | 
				
			||||||
 | 
					                        "hdd": 146,
 | 
				
			||||||
 | 
					                        "sdd": 0,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
                    "hostname": "",
 | 
					                    "hostname": "",
 | 
				
			||||||
                    "status": "REQUESTED_NEW",
 | 
					                    "status": "REQUESTED_NEW",
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,11 +6,7 @@ from os.path import dirname
 | 
				
			||||||
BASE_DIR = dirname(dirname(__file__))
 | 
					BASE_DIR = dirname(dirname(__file__))
 | 
				
			||||||
sys.path.insert(0, BASE_DIR)
 | 
					sys.path.insert(0, BASE_DIR)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from main import (
 | 
					from main import dead_host_detection, dead_host_mitigation, config
 | 
				
			||||||
    dead_host_detection,
 | 
					 | 
				
			||||||
    dead_host_mitigation,
 | 
					 | 
				
			||||||
    config
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestDeadHostMechanism(unittest.TestCase):
 | 
					class TestDeadHostMechanism(unittest.TestCase):
 | 
				
			||||||
| 
						 | 
					@ -52,13 +48,23 @@ class TestDeadHostMechanism(unittest.TestCase):
 | 
				
			||||||
            "last_heartbeat": datetime(2011, 1, 1).isoformat(),
 | 
					            "last_heartbeat": datetime(2011, 1, 1).isoformat(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        with self.client.client.lock("lock"):
 | 
					        with self.client.client.lock("lock"):
 | 
				
			||||||
            self.client.put(f"{self.host_prefix}/1", host1, value_in_json=True)
 | 
					            self.client.put(
 | 
				
			||||||
            self.client.put(f"{self.host_prefix}/2", host2, value_in_json=True)
 | 
					                f"{self.host_prefix}/1", host1, value_in_json=True
 | 
				
			||||||
            self.client.put(f"{self.host_prefix}/3", host3, value_in_json=True)
 | 
					            )
 | 
				
			||||||
            self.client.put(f"{self.host_prefix}/4", host4, value_in_json=True)
 | 
					            self.client.put(
 | 
				
			||||||
 | 
					                f"{self.host_prefix}/2", host2, value_in_json=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            self.client.put(
 | 
				
			||||||
 | 
					                f"{self.host_prefix}/3", host3, value_in_json=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            self.client.put(
 | 
				
			||||||
 | 
					                f"{self.host_prefix}/4", host4, value_in_json=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_dead_host_detection(self):
 | 
					    def test_dead_host_detection(self):
 | 
				
			||||||
        hosts = self.client.get_prefix(self.host_prefix, value_in_json=True)
 | 
					        hosts = self.client.get_prefix(
 | 
				
			||||||
 | 
					            self.host_prefix, value_in_json=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        deads = dead_host_detection(hosts)
 | 
					        deads = dead_host_detection(hosts)
 | 
				
			||||||
        self.assertEqual(deads, ["/test/host/2", "/test/host/3"])
 | 
					        self.assertEqual(deads, ["/test/host/2", "/test/host/3"])
 | 
				
			||||||
        return deads
 | 
					        return deads
 | 
				
			||||||
| 
						 | 
					@ -66,7 +72,9 @@ class TestDeadHostMechanism(unittest.TestCase):
 | 
				
			||||||
    def test_dead_host_mitigation(self):
 | 
					    def test_dead_host_mitigation(self):
 | 
				
			||||||
        deads = self.test_dead_host_detection()
 | 
					        deads = self.test_dead_host_detection()
 | 
				
			||||||
        dead_host_mitigation(self.client, deads)
 | 
					        dead_host_mitigation(self.client, deads)
 | 
				
			||||||
        hosts = self.client.get_prefix(self.host_prefix, value_in_json=True)
 | 
					        hosts = self.client.get_prefix(
 | 
				
			||||||
 | 
					            self.host_prefix, value_in_json=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        deads = dead_host_detection(hosts)
 | 
					        deads = dead_host_detection(hosts)
 | 
				
			||||||
        self.assertEqual(deads, [])
 | 
					        self.assertEqual(deads, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,16 +14,20 @@ class CustomConfigParser(configparser.RawConfigParser):
 | 
				
			||||||
            result = super().__getitem__(key)
 | 
					            result = super().__getitem__(key)
 | 
				
			||||||
        except KeyError as err:
 | 
					        except KeyError as err:
 | 
				
			||||||
            raise KeyError(
 | 
					            raise KeyError(
 | 
				
			||||||
                'Key \'{}\' not found in configuration. Make sure you configure ucloud.'.format(key)
 | 
					                "Key '{}' not found in configuration. Make sure you configure ucloud.".format(
 | 
				
			||||||
 | 
					                    key
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            ) from err
 | 
					            ) from err
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return result
 | 
					            return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Settings(object):
 | 
					class Settings(object):
 | 
				
			||||||
    def __init__(self, config_key='/uncloud/config/'):
 | 
					    def __init__(self, config_key="/uncloud/config/"):
 | 
				
			||||||
        conf_name = 'ucloud.conf'
 | 
					        conf_name = "ucloud.conf"
 | 
				
			||||||
        conf_dir = os.environ.get('UCLOUD_CONF_DIR', os.path.expanduser('~/ucloud/'))
 | 
					        conf_dir = os.environ.get(
 | 
				
			||||||
 | 
					            "UCLOUD_CONF_DIR", os.path.expanduser("~/ucloud/")
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        self.config_file = os.path.join(conf_dir, conf_name)
 | 
					        self.config_file = os.path.join(conf_dir, conf_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.config_parser = CustomConfigParser(allow_no_value=True)
 | 
					        self.config_parser = CustomConfigParser(allow_no_value=True)
 | 
				
			||||||
| 
						 | 
					@ -33,43 +37,55 @@ class Settings(object):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            self.config_parser.read(self.config_file)
 | 
					            self.config_parser.read(self.config_file)
 | 
				
			||||||
        except Exception as err:
 | 
					        except Exception as err:
 | 
				
			||||||
            logger.error('%s', err)
 | 
					            logger.error("%s", err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_etcd_client(self):
 | 
					    def get_etcd_client(self):
 | 
				
			||||||
        args = tuple()
 | 
					        args = tuple()
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            kwargs = {
 | 
					            kwargs = {
 | 
				
			||||||
                'host': self.config_parser.get('etcd', 'url'),
 | 
					                "host": self.config_parser.get("etcd", "url"),
 | 
				
			||||||
                'port': self.config_parser.get('etcd', 'port'),
 | 
					                "port": self.config_parser.get("etcd", "port"),
 | 
				
			||||||
                'ca_cert': self.config_parser.get('etcd', 'ca_cert'),
 | 
					                "ca_cert": self.config_parser.get("etcd", "ca_cert"),
 | 
				
			||||||
                'cert_cert': self.config_parser.get('etcd', 'cert_cert'),
 | 
					                "cert_cert": self.config_parser.get(
 | 
				
			||||||
                'cert_key': self.config_parser.get('etcd', 'cert_key')
 | 
					                    "etcd", "cert_cert"
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                "cert_key": self.config_parser.get("etcd", "cert_key"),
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        except configparser.Error as err:
 | 
					        except configparser.Error as err:
 | 
				
			||||||
            raise configparser.Error('{} in config file {}'.format(err.message, self.config_file)) from err
 | 
					            raise configparser.Error(
 | 
				
			||||||
 | 
					                "{} in config file {}".format(
 | 
				
			||||||
 | 
					                    err.message, self.config_file
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            ) from err
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                wrapper = Etcd3Wrapper(*args, **kwargs)
 | 
					                wrapper = Etcd3Wrapper(*args, **kwargs)
 | 
				
			||||||
            except Exception as err:
 | 
					            except Exception as err:
 | 
				
			||||||
                logger.error('etcd connection not successfull. Please check your config file.'
 | 
					                logger.error(
 | 
				
			||||||
                             '\nDetails: %s\netcd connection parameters: %s', err, kwargs)
 | 
					                    "etcd connection not successfull. Please check your config file."
 | 
				
			||||||
 | 
					                    "\nDetails: %s\netcd connection parameters: %s",
 | 
				
			||||||
 | 
					                    err,
 | 
				
			||||||
 | 
					                    kwargs,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                sys.exit(1)
 | 
					                sys.exit(1)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                return wrapper
 | 
					                return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def read_internal_values(self):
 | 
					    def read_internal_values(self):
 | 
				
			||||||
        self.config_parser.read_dict({
 | 
					        self.config_parser.read_dict(
 | 
				
			||||||
            'etcd': {
 | 
					            {
 | 
				
			||||||
                'file_prefix': '/files/',
 | 
					                "etcd": {
 | 
				
			||||||
                'host_prefix': '/hosts/',
 | 
					                    "file_prefix": "/files/",
 | 
				
			||||||
                'image_prefix': '/images/',
 | 
					                    "host_prefix": "/hosts/",
 | 
				
			||||||
                'image_store_prefix': '/imagestore/',
 | 
					                    "image_prefix": "/images/",
 | 
				
			||||||
                'network_prefix': '/networks/',
 | 
					                    "image_store_prefix": "/imagestore/",
 | 
				
			||||||
                'request_prefix': '/requests/',
 | 
					                    "network_prefix": "/networks/",
 | 
				
			||||||
                'user_prefix': '/users/',
 | 
					                    "request_prefix": "/requests/",
 | 
				
			||||||
                'vm_prefix': '/vms/',
 | 
					                    "user_prefix": "/users/",
 | 
				
			||||||
 | 
					                    "vm_prefix": "/vms/",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
        })
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def read_config_file_values(self, config_file):
 | 
					    def read_config_file_values(self, config_file):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
| 
						 | 
					@ -77,18 +93,26 @@ class Settings(object):
 | 
				
			||||||
            with open(config_file, "r") as config_file_handle:
 | 
					            with open(config_file, "r") as config_file_handle:
 | 
				
			||||||
                self.config_parser.read_file(config_file_handle)
 | 
					                self.config_parser.read_file(config_file_handle)
 | 
				
			||||||
        except FileNotFoundError:
 | 
					        except FileNotFoundError:
 | 
				
			||||||
            sys.exit('Configuration file {} not found!'.format(config_file))
 | 
					            sys.exit(
 | 
				
			||||||
 | 
					                "Configuration file {} not found!".format(config_file)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        except Exception as err:
 | 
					        except Exception as err:
 | 
				
			||||||
            logger.exception(err)
 | 
					            logger.exception(err)
 | 
				
			||||||
            sys.exit("Error occurred while reading configuration file")
 | 
					            sys.exit("Error occurred while reading configuration file")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def read_values_from_etcd(self):
 | 
					    def read_values_from_etcd(self):
 | 
				
			||||||
        etcd_client = self.get_etcd_client()
 | 
					        etcd_client = self.get_etcd_client()
 | 
				
			||||||
        config_from_etcd = etcd_client.get(self.config_key, value_in_json=True)
 | 
					        config_from_etcd = etcd_client.get(
 | 
				
			||||||
 | 
					            self.config_key, value_in_json=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        if config_from_etcd:
 | 
					        if config_from_etcd:
 | 
				
			||||||
            self.config_parser.read_dict(config_from_etcd.value)
 | 
					            self.config_parser.read_dict(config_from_etcd.value)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise KeyError("Key '{}' not found in etcd. Please configure ucloud.".format(self.config_key))
 | 
					            raise KeyError(
 | 
				
			||||||
 | 
					                "Key '{}' not found in etcd. Please configure ucloud.".format(
 | 
				
			||||||
 | 
					                    self.config_key
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __getitem__(self, key):
 | 
					    def __getitem__(self, key):
 | 
				
			||||||
        self.read_values_from_etcd()
 | 
					        self.read_values_from_etcd()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,15 +12,19 @@ class Shared:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def host_pool(self):
 | 
					    def host_pool(self):
 | 
				
			||||||
        return HostPool(self.etcd_client, settings['etcd']['host_prefix'])
 | 
					        return HostPool(
 | 
				
			||||||
 | 
					            self.etcd_client, settings["etcd"]["host_prefix"]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def vm_pool(self):
 | 
					    def vm_pool(self):
 | 
				
			||||||
        return VmPool(self.etcd_client, settings['etcd']['vm_prefix'])
 | 
					        return VmPool(self.etcd_client, settings["etcd"]["vm_prefix"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def request_pool(self):
 | 
					    def request_pool(self):
 | 
				
			||||||
        return RequestPool(self.etcd_client, settings['etcd']['request_prefix'])
 | 
					        return RequestPool(
 | 
				
			||||||
 | 
					            self.etcd_client, settings["etcd"]["request_prefix"]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def storage_handler(self):
 | 
					    def storage_handler(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,9 @@ class VMQMPHandles:
 | 
				
			||||||
        self.sock.close()
 | 
					        self.sock.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if exc_type:
 | 
					        if exc_type:
 | 
				
			||||||
            logger.error('Couldn\'t get handle for VM.', exc_type, exc_val, exc_tb)
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Couldn't get handle for VM.", exc_type, exc_val, exc_tb
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            raise exc_type("Couldn't get handle for VM.") from exc_type
 | 
					            raise exc_type("Couldn't get handle for VM.") from exc_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,29 +56,46 @@ class TransferVM(Process):
 | 
				
			||||||
        with suppress(FileNotFoundError):
 | 
					        with suppress(FileNotFoundError):
 | 
				
			||||||
            os.remove(self.src_sock_path)
 | 
					            os.remove(self.src_sock_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        command = ['ssh', '-nNT', '-L', '{}:{}'.format(self.src_sock_path, self.dest_sock_path),
 | 
					        command = [
 | 
				
			||||||
                   'root@{}'.format(self.host)]
 | 
					            "ssh",
 | 
				
			||||||
 | 
					            "-nNT",
 | 
				
			||||||
 | 
					            "-L",
 | 
				
			||||||
 | 
					            "{}:{}".format(self.src_sock_path, self.dest_sock_path),
 | 
				
			||||||
 | 
					            "root@{}".format(self.host),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            p = sp.Popen(command)
 | 
					            p = sp.Popen(command)
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            logger.error('Couldn\' forward unix socks over ssh.', exc_info=e)
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Couldn' forward unix socks over ssh.", exc_info=e
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            time.sleep(2)
 | 
					            time.sleep(2)
 | 
				
			||||||
            vmm = VMM()
 | 
					            vmm = VMM()
 | 
				
			||||||
            logger.debug('Executing: ssh forwarding command: %s', command)
 | 
					            logger.debug(
 | 
				
			||||||
            vmm.execute_command(self.src_uuid, command='migrate',
 | 
					                "Executing: ssh forwarding command: %s", command
 | 
				
			||||||
                                arguments={'uri': 'unix:{}'.format(self.src_sock_path)})
 | 
					            )
 | 
				
			||||||
 | 
					            vmm.execute_command(
 | 
				
			||||||
 | 
					                self.src_uuid,
 | 
				
			||||||
 | 
					                command="migrate",
 | 
				
			||||||
 | 
					                arguments={"uri": "unix:{}".format(self.src_sock_path)},
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            while p.poll() is None:
 | 
					            while p.poll() is None:
 | 
				
			||||||
                success, output = vmm.execute_command(self.src_uuid, command='query-migrate')
 | 
					                success, output = vmm.execute_command(
 | 
				
			||||||
 | 
					                    self.src_uuid, command="query-migrate"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                if success:
 | 
					                if success:
 | 
				
			||||||
                    status = output['return']['status']
 | 
					                    status = output["return"]["status"]
 | 
				
			||||||
                    if status != 'active':
 | 
					
 | 
				
			||||||
                        print('Migration Status: ', status)
 | 
					                    if status != "active":
 | 
				
			||||||
 | 
					                        print("Migration Status: ", status)
 | 
				
			||||||
 | 
					                        if status == "completed":
 | 
				
			||||||
 | 
					                            vmm.stop(self.src_uuid)
 | 
				
			||||||
                        return
 | 
					                        return
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        print('Migration Status: ', status)
 | 
					                        print("Migration Status: ", status)
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    return
 | 
					                    return
 | 
				
			||||||
                time.sleep(0.2)
 | 
					                time.sleep(0.2)
 | 
				
			||||||
| 
						 | 
					@ -84,18 +103,29 @@ class TransferVM(Process):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VMM:
 | 
					class VMM:
 | 
				
			||||||
    # Virtual Machine Manager
 | 
					    # Virtual Machine Manager
 | 
				
			||||||
    def __init__(self, qemu_path='/usr/bin/qemu-system-x86_64',
 | 
					    def __init__(
 | 
				
			||||||
                 vmm_backend=os.path.expanduser('~/ucloud/vmm/')):
 | 
					        self,
 | 
				
			||||||
 | 
					        qemu_path="/usr/bin/qemu-system-x86_64",
 | 
				
			||||||
 | 
					        vmm_backend=os.path.expanduser("~/ucloud/vmm/"),
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
        self.qemu_path = qemu_path
 | 
					        self.qemu_path = qemu_path
 | 
				
			||||||
        self.vmm_backend = vmm_backend
 | 
					        self.vmm_backend = vmm_backend
 | 
				
			||||||
        self.socket_dir = os.path.join(self.vmm_backend, 'sock')
 | 
					        self.socket_dir = os.path.join(self.vmm_backend, "sock")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not os.path.isdir(self.vmm_backend):
 | 
					        if not os.path.isdir(self.vmm_backend):
 | 
				
			||||||
            logger.info('{} does not exists. Creating it...'.format(self.vmm_backend))
 | 
					            logger.info(
 | 
				
			||||||
 | 
					                "{} does not exists. Creating it...".format(
 | 
				
			||||||
 | 
					                    self.vmm_backend
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            os.makedirs(self.vmm_backend, exist_ok=True)
 | 
					            os.makedirs(self.vmm_backend, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not os.path.isdir(self.socket_dir):
 | 
					        if not os.path.isdir(self.socket_dir):
 | 
				
			||||||
            logger.info('{} does not exists. Creating it...'.format(self.socket_dir))
 | 
					            logger.info(
 | 
				
			||||||
 | 
					                "{} does not exists. Creating it...".format(
 | 
				
			||||||
 | 
					                    self.socket_dir
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            os.makedirs(self.socket_dir, exist_ok=True)
 | 
					            os.makedirs(self.socket_dir, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_running(self, uuid):
 | 
					    def is_running(self, uuid):
 | 
				
			||||||
| 
						 | 
					@ -106,8 +136,12 @@ class VMM:
 | 
				
			||||||
            recv = sock.recv(4096)
 | 
					            recv = sock.recv(4096)
 | 
				
			||||||
        except Exception as err:
 | 
					        except Exception as err:
 | 
				
			||||||
            # unix sock doesn't exists or it is closed
 | 
					            # unix sock doesn't exists or it is closed
 | 
				
			||||||
            logger.debug('VM {} sock either don\' exists or it is closed. It mean VM is stopped.'.format(uuid),
 | 
					            logger.debug(
 | 
				
			||||||
                         exc_info=err)
 | 
					                "VM {} sock either don' exists or it is closed. It mean VM is stopped.".format(
 | 
				
			||||||
 | 
					                    uuid
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                exc_info=err,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # if we receive greetings from qmp it mean VM is running
 | 
					            # if we receive greetings from qmp it mean VM is running
 | 
				
			||||||
            if len(recv) > 0:
 | 
					            if len(recv) > 0:
 | 
				
			||||||
| 
						 | 
					@ -122,36 +156,67 @@ class VMM:
 | 
				
			||||||
        # start --> sucess?
 | 
					        # start --> sucess?
 | 
				
			||||||
        migration_args = ()
 | 
					        migration_args = ()
 | 
				
			||||||
        if migration:
 | 
					        if migration:
 | 
				
			||||||
            migration_args = ('-incoming', 'unix:{}'.format(os.path.join(self.socket_dir, uuid)))
 | 
					            migration_args = (
 | 
				
			||||||
 | 
					                "-incoming",
 | 
				
			||||||
 | 
					                "unix:{}".format(os.path.join(self.socket_dir, uuid)),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.is_running(uuid):
 | 
					        if self.is_running(uuid):
 | 
				
			||||||
            logger.warning('Cannot start VM. It is already running.')
 | 
					            logger.warning("Cannot start VM. It is already running.")
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            qmp_arg = ('-qmp', 'unix:{},server,nowait'.format(join_path(self.vmm_backend, uuid)))
 | 
					            qmp_arg = (
 | 
				
			||||||
            vnc_arg = ('-vnc', 'unix:{}'.format(tempfile.NamedTemporaryFile().name))
 | 
					                "-qmp",
 | 
				
			||||||
 | 
					                "unix:{},server,nowait".format(
 | 
				
			||||||
 | 
					                    join_path(self.vmm_backend, uuid)
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            vnc_arg = (
 | 
				
			||||||
 | 
					                "-vnc",
 | 
				
			||||||
 | 
					                "unix:{}".format(tempfile.NamedTemporaryFile().name),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            command = ['sudo', '-p', 'Enter password to start VM {}: '.format(uuid),
 | 
					            command = [
 | 
				
			||||||
                       self.qemu_path, *args, *qmp_arg, *migration_args, *vnc_arg, '-daemonize']
 | 
					                "sudo",
 | 
				
			||||||
 | 
					                "-p",
 | 
				
			||||||
 | 
					                "Enter password to start VM {}: ".format(uuid),
 | 
				
			||||||
 | 
					                self.qemu_path,
 | 
				
			||||||
 | 
					                *args,
 | 
				
			||||||
 | 
					                *qmp_arg,
 | 
				
			||||||
 | 
					                *migration_args,
 | 
				
			||||||
 | 
					                *vnc_arg,
 | 
				
			||||||
 | 
					                "-daemonize",
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                sp.check_output(command, stderr=sp.PIPE)
 | 
					                sp.check_output(command, stderr=sp.PIPE)
 | 
				
			||||||
            except sp.CalledProcessError as err:
 | 
					            except sp.CalledProcessError as err:
 | 
				
			||||||
                logger.exception('Error occurred while starting VM.\nDetail %s', err.stderr.decode('utf-8'))
 | 
					                logger.exception(
 | 
				
			||||||
 | 
					                    "Error occurred while starting VM.\nDetail %s",
 | 
				
			||||||
 | 
					                    err.stderr.decode("utf-8"),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                with suppress(sp.CalledProcessError):
 | 
					                with suppress(sp.CalledProcessError):
 | 
				
			||||||
                    sp.check_output([
 | 
					                    sp.check_output(
 | 
				
			||||||
                        'sudo', '-p',
 | 
					                        [
 | 
				
			||||||
                        'Enter password to correct permission for uncloud-vmm\'s directory',
 | 
					                            "sudo",
 | 
				
			||||||
                        'chmod', '-R', 'o=rwx,g=rwx', self.vmm_backend
 | 
					                            "-p",
 | 
				
			||||||
                    ])
 | 
					                            "Enter password to correct permission for uncloud-vmm's directory",
 | 
				
			||||||
 | 
					                            "chmod",
 | 
				
			||||||
 | 
					                            "-R",
 | 
				
			||||||
 | 
					                            "o=rwx,g=rwx",
 | 
				
			||||||
 | 
					                            self.vmm_backend,
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # TODO: Find some good way to check whether the virtual machine is up and
 | 
					                # TODO: Find some good way to check whether the virtual machine is up and
 | 
				
			||||||
                #       running without relying on non-guarenteed ways.
 | 
					                #       running without relying on non-guarenteed ways.
 | 
				
			||||||
                for _ in range(10):
 | 
					                for _ in range(10):
 | 
				
			||||||
                    time.sleep(2)
 | 
					                    time.sleep(2)
 | 
				
			||||||
                    status = self.get_status(uuid)
 | 
					                    status = self.get_status(uuid)
 | 
				
			||||||
                    if status in ['running', 'inmigrate']:
 | 
					                    if status in ["running", "inmigrate"]:
 | 
				
			||||||
                        return status
 | 
					                        return status
 | 
				
			||||||
                logger.warning('Timeout on VM\'s status. Shutting down VM %s', uuid)
 | 
					                logger.warning(
 | 
				
			||||||
 | 
					                    "Timeout on VM's status. Shutting down VM %s", uuid
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                self.stop(uuid)
 | 
					                self.stop(uuid)
 | 
				
			||||||
                # TODO: What should we do more. VM can still continue to run in background.
 | 
					                # TODO: What should we do more. VM can still continue to run in background.
 | 
				
			||||||
                #       If we have pid of vm we can kill it using OS.
 | 
					                #       If we have pid of vm we can kill it using OS.
 | 
				
			||||||
| 
						 | 
					@ -159,51 +224,69 @@ class VMM:
 | 
				
			||||||
    def execute_command(self, uuid, command, **kwargs):
 | 
					    def execute_command(self, uuid, command, **kwargs):
 | 
				
			||||||
        # execute_command -> sucess?, output
 | 
					        # execute_command -> sucess?, output
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            with VMQMPHandles(os.path.join(self.vmm_backend, uuid)) as (sock_handle, file_handle):
 | 
					            with VMQMPHandles(os.path.join(self.vmm_backend, uuid)) as (
 | 
				
			||||||
                command_to_execute = {
 | 
					                sock_handle,
 | 
				
			||||||
                    'execute': command,
 | 
					                file_handle,
 | 
				
			||||||
                    **kwargs
 | 
					            ):
 | 
				
			||||||
                }
 | 
					                command_to_execute = {"execute": command, **kwargs}
 | 
				
			||||||
                sock_handle.sendall(json.dumps(command_to_execute).encode('utf-8'))
 | 
					                sock_handle.sendall(
 | 
				
			||||||
 | 
					                    json.dumps(command_to_execute).encode("utf-8")
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                output = file_handle.readline()
 | 
					                output = file_handle.readline()
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            logger.exception('Error occurred while executing command and getting valid output from qmp')
 | 
					            logger.exception(
 | 
				
			||||||
 | 
					                "Error occurred while executing command and getting valid output from qmp"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                output = json.loads(output)
 | 
					                output = json.loads(output)
 | 
				
			||||||
            except Exception:
 | 
					            except Exception:
 | 
				
			||||||
                logger.exception('QMP Output isn\'t valid JSON. %s', output)
 | 
					                logger.exception(
 | 
				
			||||||
 | 
					                    "QMP Output isn't valid JSON. %s", output
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                return 'return' in output, output
 | 
					                return "return" in output, output
 | 
				
			||||||
        return False, None
 | 
					        return False, None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def stop(self, uuid):
 | 
					    def stop(self, uuid):
 | 
				
			||||||
        success, output = self.execute_command(command='quit', uuid=uuid)
 | 
					        success, output = self.execute_command(
 | 
				
			||||||
 | 
					            command="quit", uuid=uuid
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        return success
 | 
					        return success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_status(self, uuid):
 | 
					    def get_status(self, uuid):
 | 
				
			||||||
        success, output = self.execute_command(command='query-status', uuid=uuid)
 | 
					        success, output = self.execute_command(
 | 
				
			||||||
 | 
					            command="query-status", uuid=uuid
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        if success:
 | 
					        if success:
 | 
				
			||||||
            return output['return']['status']
 | 
					            return output["return"]["status"]
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # TODO: Think about this for a little more
 | 
					            # TODO: Think about this for a little more
 | 
				
			||||||
            return 'STOPPED'
 | 
					            return "STOPPED"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def discover(self):
 | 
					    def discover(self):
 | 
				
			||||||
        vms = [
 | 
					        vms = [
 | 
				
			||||||
            uuid for uuid in os.listdir(self.vmm_backend)
 | 
					            uuid
 | 
				
			||||||
 | 
					            for uuid in os.listdir(self.vmm_backend)
 | 
				
			||||||
            if not isdir(join_path(self.vmm_backend, uuid))
 | 
					            if not isdir(join_path(self.vmm_backend, uuid))
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        return vms
 | 
					        return vms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_vnc(self, uuid):
 | 
					    def get_vnc(self, uuid):
 | 
				
			||||||
        success, output = self.execute_command(uuid, command='query-vnc')
 | 
					        success, output = self.execute_command(
 | 
				
			||||||
 | 
					            uuid, command="query-vnc"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        if success:
 | 
					        if success:
 | 
				
			||||||
            return output['return']['service']
 | 
					            return output["return"]["service"]
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def transfer(self, src_uuid, destination_sock_path, host):
 | 
					    def transfer(self, src_uuid, destination_sock_path, host):
 | 
				
			||||||
        p = TransferVM(src_uuid, destination_sock_path, socket_dir=self.socket_dir, host=host)
 | 
					        p = TransferVM(
 | 
				
			||||||
 | 
					            src_uuid,
 | 
				
			||||||
 | 
					            destination_sock_path,
 | 
				
			||||||
 | 
					            socket_dir=self.socket_dir,
 | 
				
			||||||
 | 
					            host=host,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        p.start()
 | 
					        p.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # TODO: the following method should clean things that went wrong
 | 
					    # TODO: the following method should clean things that went wrong
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue