Beispiel #1
0
def resolve(name):
    resp = requests.get(CIRROS_URL,
                        headers={'User-Agent': util.get_user_agent()})
    if resp.status_code != 200:
        raise exceptions.HTTPError(
            'Failed to fetch http://download.cirros-cloud.net/, '
            'status code %d' % resp.status_code)

    if name == 'cirros':
        versions = []
        dir_re = re.compile(r'.*<a href="([0-9]+\.[0-9]+\.[0-9]+)/">.*/</a>.*')
        for line in resp.text.split('\n'):
            m = dir_re.match(line)
            if m:
                versions.append(m.group(1))
        LOG.withField('versions', versions).info('Found cirros versions')
        vernum = versions[-1]
    else:
        try:
            # Name is assumed to be in the form cirros:0.4.0
            _, vernum = name.split(':')
        except Exception:
            raise exceptions.VersionSpecificationError(
                'Cannot parse version: %s' % name)

    url = config.parsed.get('DOWNLOAD_URL_CIRROS') % {'vernum': vernum}
    log = LOG.withField('url', url)

    # Retrieve check sum file
    checksum_url = CIRROS_URL + '/' + vernum + '/MD5SUMS'
    resp = requests.get(checksum_url,
                        headers={'User-Agent': util.get_user_agent()})
    log.withField('checksum_url', checksum_url).withField(
        'resp', resp).debug("Checksum request response")
    if resp.status_code != 200:
        # Cirros does not always have a checksum file available
        log.warning('Unable to retrieve MD5SUMS')
        return url, None

    sum_re = re.compile(r'^([0-9a-f]+) .*' + 'cirros-' + vernum +
                        '-x86_64-disk.img')
    checksum = None
    for line in resp.text.split('\n'):
        m = sum_re.match(line)
        if m:
            checksum = m.group(1)
            break
    if not checksum_url:
        log.warning('Did not find checksum')

    log.withField('checksum', checksum).info('Checksum retrieval')

    return (url, checksum)
Beispiel #2
0
    def wrapper(*args, **kwargs):
        if config.parsed.get('NODE_IP') != config.parsed.get(
                'NETWORK_NODE_IP'):
            admin_token = util.get_api_token(
                'http://%s:%d' % (config.parsed.get('NETWORK_NODE_IP'),
                                  config.parsed.get('API_PORT')),
                namespace='system')
            r = requests.request(flask.request.environ['REQUEST_METHOD'],
                                 'http://%s:%d%s' %
                                 (config.parsed.get('NETWORK_NODE_IP'),
                                  config.parsed.get('API_PORT'),
                                  flask.request.environ['PATH_INFO']),
                                 data=flask.request.data,
                                 headers={
                                     'Authorization': admin_token,
                                     'User-Agent': util.get_user_agent()
                                 })

            logutil.info(
                None,
                'Returning proxied request: %d, %s' % (r.status_code, r.text))
            resp = flask.Response(r.text, mimetype='application/json')
            resp.status_code = r.status_code
            return resp

        return func(*args, **kwargs)
Beispiel #3
0
    def wrapper(*args, **kwargs):
        i = kwargs.get('instance_from_db_virt')
        if i and i.db_entry['node'] != config.parsed.get('NODE_NAME'):
            url = 'http://%s:%d%s' % (i.db_entry['node'],
                                      config.parsed.get('API_PORT'),
                                      flask.request.environ['PATH_INFO'])
            api_token = util.get_api_token(
                'http://%s:%d' %
                (i.db_entry['node'], config.parsed.get('API_PORT')),
                namespace=get_jwt_identity())
            r = requests.request(flask.request.environ['REQUEST_METHOD'],
                                 url,
                                 data=json.dumps(flask_get_post_body()),
                                 headers={
                                     'Authorization': api_token,
                                     'User-Agent': util.get_user_agent()
                                 })

            logutil.info(
                None, 'Proxied %s %s returns: %d, %s' %
                (flask.request.environ['REQUEST_METHOD'], url, r.status_code,
                 r.text))
            resp = flask.Response(r.text, mimetype='application/json')
            resp.status_code = r.status_code
            return resp

        return func(*args, **kwargs)
def resolve(name):
    resp = requests.get(CIRROS_URL,
                        headers={'User-Agent': util.get_user_agent()})
    if resp.status_code != 200:
        raise exceptions.HTTPError(
            'Failed to fetch http://download.cirros-cloud.net/, '
            'status code %d' % resp.status_code)

    if name == 'cirros':
        versions = []
        dir_re = re.compile(r'.*<a href="([0-9]+\.[0-9]+\.[0-9]+)/">.*/</a>.*')
        for line in resp.text.split('\n'):
            m = dir_re.match(line)
            if m:
                versions.append(m.group(1))
        LOG.info('Found cirros versions: %s' % versions)
        vernum = versions[-1]
    else:
        try:
            # Name is assumed to be in the form cirros:0.4.0
            _, vernum = name.split(':')
        except Exception:
            raise exceptions.VersionSpecificationError(
                'Cannot parse version: %s' % name)

    return (config.parsed.get('DOWNLOAD_URL_CIRROS') % {'vernum': vernum})
Beispiel #5
0
    def post(self,
             netblock=None,
             provide_dhcp=None,
             provide_nat=None,
             name=None,
             namespace=None):
        try:
            ipaddress.ip_network(netblock)
        except ValueError as e:
            return error(400, 'cannot parse netblock: %s' % e)

        if not namespace:
            namespace = get_jwt_identity()

        # If accessing a foreign name namespace, we need to be an admin
        if get_jwt_identity() not in [namespace, 'system']:
            return error(
                401,
                'only admins can create resources in a different namespace')

        network = db.allocate_network(netblock, provide_dhcp, provide_nat,
                                      name, namespace)
        db.add_event('network', network['uuid'], 'api', 'create', None, None)

        # Networks should immediately appear on the network node
        with db.get_lock('sf/network/%s' % network['uuid'], ttl=900) as _:
            if config.parsed.get('NODE_IP') == config.parsed.get(
                    'NETWORK_NODE_IP'):
                n = net.from_db(network['uuid'])
                if not n:
                    LOG.info(
                        'network(%s): network not found, genuinely missing' %
                        network['uuid'])
                    return error(404, 'network not found')

                n.create()
                n.ensure_mesh()
            else:
                admin_token = util.get_api_token(
                    'http://%s:%d' % (config.parsed.get('NETWORK_NODE_IP'),
                                      config.parsed.get('API_PORT')),
                    namespace=namespace)
                requests.request('put', ('http://%s:%d/deploy_network_node' %
                                         (config.parsed.get('NETWORK_NODE_IP'),
                                          config.parsed.get('API_PORT'))),
                                 data=json.dumps({'uuid': network['uuid']}),
                                 headers={
                                     'Authorization': admin_token,
                                     'User-Agent': util.get_user_agent()
                                 })

            db.add_event('network', network['uuid'], 'api', 'created', None,
                         None)
            db.update_network_state(network['uuid'], 'created')

            # Initialise metadata
            db.persist_metadata('network', network['uuid'], {})

        return network
Beispiel #6
0
 def _open_connection(self):
     resp = requests.get(self.url,
                         allow_redirects=True,
                         stream=True,
                         headers={'User-Agent': util.get_user_agent()})
     if resp.status_code != 200:
         raise exceptions.HTTPError(
             'Failed to fetch HEAD of %s (status code %d)' %
             (self.url, resp.status_code))
     return resp
Beispiel #7
0
    def _requires_fetch(self):
        resp = requests.get(self.url,
                            allow_redirects=True,
                            stream=True,
                            headers={'User-Agent': util.get_user_agent()})
        if resp.status_code != 200:
            raise exceptions.HTTPError(
                'Failed to fetch HEAD of %s (status code %d)' %
                (self.url, resp.status_code))

        dirty_fields = {}
        for field in VALIDATED_IMAGE_FIELDS:
            if self.info.get(field) != resp.headers.get(field):
                dirty_fields[field] = {
                    'before': self.info.get(field),
                    'after': resp.headers.get(field)
                }

        return dirty_fields, resp
def resolve(name):
    resp = requests.get(UBUNTU_URL,
                        headers={'User-Agent': util.get_user_agent()})
    if resp.status_code != 200:
        raise exceptions.HTTPError('Failed to fetch %s, status code %d' %
                                   (UBUNTU_URL, resp.status_code))

    num_to_name = {}
    name_to_num = {}
    dir_re = re.compile(
        r'.*<a href="(.*)/">.*Ubuntu Server ([0-9]+\.[0-9]+).*')
    for line in resp.text.split('\n'):
        m = dir_re.match(line)
        if m:
            num_to_name[m.group(2)] = m.group(1)
            name_to_num[m.group(1)] = m.group(2)
    logutil.info(None, 'Found ubuntu versions: %s' % num_to_name)

    vernum = None
    vername = None

    if name == 'ubuntu':
        vernum = sorted(num_to_name.keys())[-1]
        vername = num_to_name[vernum]
    else:
        try:
            # Name is assumed to be in the form ubuntu:18.04 or ubuntu:bionic
            _, version = name.split(':')
            if version in num_to_name:
                vernum = version
                vername = num_to_name[version]
            else:
                vername = version
                vernum = name_to_num[version]
        except Exception:
            raise exceptions.VersionSpecificationError(
                'Cannot parse version: %s' % name)

    return (config.parsed.get('DOWNLOAD_URL_UBUNTU') % {
        'vernum': vernum,
        'vername': vername
    })
Beispiel #9
0
 def remove_dhcp(self):
     if config.parsed.get('NODE_IP') == config.parsed.get('NETWORK_NODE_IP'):
         subst = self.subst_dict()
         with util.RecordedOperation('remove dhcp', self) as _:
             with db.get_lock('sf/net/%s' % self.uuid, ttl=120) as _:
                 d = dhcp.DHCP(self.uuid, subst['vx_veth_inner'])
                 d.remove_dhcpd()
     else:
         admin_token = util.get_api_token(
             'http://%s:%d' % (config.parsed.get('NETWORK_NODE_IP'),
                               config.parsed.get('API_PORT')),
             namespace='system')
         requests.request(
             'put',
             ('http://%s:%d/remove_dhcp'
              % (config.parsed.get('NETWORK_NODE_IP'),
                 config.parsed.get('API_PORT'))),
             data=json.dumps({'uuid': self.uuid}),
             headers={'Authorization': admin_token,
                      'User-Agent': util.get_user_agent()})
Beispiel #10
0
def requires_fetch(image_url):
    hashed_image_url = _hash_image_url(image_url)
    hashed_image_path = os.path.join(_get_cache_path(), hashed_image_url)
    info = _read_info(image_url, hashed_image_url, hashed_image_path)

    resp = requests.get(image_url,
                        allow_redirects=True,
                        stream=True,
                        headers={'User-Agent': util.get_user_agent()})
    if resp.status_code != 200:
        raise exceptions.HTTPError(
            'Failed to fetch HEAD of %s (status code %d)' %
            (image_url, resp.status_code))

    image_dirty = False
    for field in VALIDATED_IMAGE_FIELDS:
        if info.get(field) != resp.headers.get(field):
            image_dirty = True

    return hashed_image_path, info, image_dirty, resp
Beispiel #11
0
    def create(self):
        subst = self.subst_dict()

        with db.get_lock('sf/net/%s' % self.uuid, ttl=120) as _:
            if not util.check_for_interface(subst['vx_interface']):
                with util.RecordedOperation('create vxlan interface', self) as _:
                    processutils.execute(
                        'ip link add %(vx_interface)s type vxlan id %(vx_id)s '
                        'dev %(physical_interface)s dstport 0'
                        % subst, shell=True)
                    processutils.execute(
                        'sysctl -w net.ipv4.conf.%(vx_interface)s.arp_notify=1' % subst,
                        shell=True)

            if not util.check_for_interface(subst['vx_bridge']):
                with util.RecordedOperation('create vxlan bridge', self) as _:
                    processutils.execute(
                        'ip link add %(vx_bridge)s type bridge' % subst, shell=True)
                    processutils.execute(
                        'ip link set %(vx_interface)s master %(vx_bridge)s' % subst,
                        shell=True)
                    processutils.execute(
                        'ip link set %(vx_interface)s up' % subst, shell=True)
                    processutils.execute(
                        'ip link set %(vx_bridge)s up' % subst, shell=True)
                    processutils.execute(
                        'sysctl -w net.ipv4.conf.%(vx_bridge)s.arp_notify=1' % subst,
                        shell=True)
                    processutils.execute(
                        'brctl setfd %(vx_bridge)s 0' % subst, shell=True)
                    processutils.execute(
                        'brctl stp %(vx_bridge)s off' % subst, shell=True)
                    processutils.execute(
                        'brctl setageing %(vx_bridge)s 0' % subst, shell=True)

        if config.parsed.get('NODE_IP') == config.parsed.get('NETWORK_NODE_IP'):
            if not os.path.exists('/var/run/netns/%(netns)s' % subst):
                with util.RecordedOperation('create netns', self) as _:
                    processutils.execute(
                        'ip netns add %(netns)s' % subst, shell=True)

            if not util.check_for_interface(subst['vx_veth_outer']):
                with util.RecordedOperation('create router veth', self) as _:
                    processutils.execute(
                        'ip link add %(vx_veth_outer)s type veth peer name %(vx_veth_inner)s' % subst,
                        shell=True)
                    processutils.execute(
                        'ip link set %(vx_veth_inner)s netns %(netns)s' % subst, shell=True)
                    processutils.execute(
                        'brctl addif %(vx_bridge)s %(vx_veth_outer)s' % subst, shell=True)
                    processutils.execute(
                        'ip link set %(vx_veth_outer)s up' % subst, shell=True)
                    processutils.execute(
                        '%(in_netns)s ip link set %(vx_veth_inner)s up' % subst, shell=True)
                    processutils.execute(
                        '%(in_netns)s ip addr add %(router)s/%(netmask)s dev %(vx_veth_inner)s' % subst,
                        shell=True)

            if not util.check_for_interface(subst['physical_veth_outer']):
                with util.RecordedOperation('create physical veth', self) as _:
                    processutils.execute(
                        'ip link add %(physical_veth_outer)s type veth peer name '
                        '%(physical_veth_inner)s' % subst,
                        shell=True)
                    processutils.execute(
                        'brctl addif %(physical_bridge)s %(physical_veth_outer)s' % subst,
                        shell=True)
                    processutils.execute(
                        'ip link set %(physical_veth_outer)s up' % subst, shell=True)
                    processutils.execute(
                        'ip link set %(physical_veth_inner)s netns %(netns)s' % subst,
                        shell=True)

            self.deploy_nat()
            self.update_dhcp()
        else:
            admin_token = util.get_api_token(
                'http://%s:%d' % (config.parsed.get('NETWORK_NODE_IP'),
                                  config.parsed.get('API_PORT')),
                namespace='system')
            requests.request(
                'put',
                ('http://%s:%d/deploy_network_node'
                 % (config.parsed.get('NETWORK_NODE_IP'),
                    config.parsed.get('API_PORT'))),
                data=json.dumps({'uuid': self.uuid}),
                headers={'Authorization': admin_token,
                         'User-Agent': util.get_user_agent()})
Beispiel #12
0
def resolve(name):
    resp = requests.get(UBUNTU_URL,
                        headers={'User-Agent': util.get_user_agent()})
    if resp.status_code != 200:
        raise exceptions.HTTPError('Failed to fetch %s, status code %d' %
                                   (UBUNTU_URL, resp.status_code))

    num_to_name = {}
    name_to_num = {}
    dir_re = re.compile(
        r'.*<a href="(.*)/">.*Ubuntu Server ([0-9]+\.[0-9]+).*')
    for line in resp.text.split('\n'):
        m = dir_re.match(line)
        if m:
            num_to_name[m.group(2)] = m.group(1)
            name_to_num[m.group(1)] = m.group(2)
    LOG.withField('versions', num_to_name).info('Found ubuntu versions')

    vernum = None
    vername = None

    if name == 'ubuntu':
        vernum = sorted(num_to_name.keys())[-1]
        vername = num_to_name[vernum]
    else:
        try:
            # Name is assumed to be in the form ubuntu:18.04 or ubuntu:bionic
            _, version = name.split(':')
            if version in num_to_name:
                vernum = version
                vername = num_to_name[version]
            else:
                vername = version
                vernum = name_to_num[version]
        except Exception:
            raise exceptions.VersionSpecificationError(
                'Cannot parse version: %s' % name)

    url = (config.parsed.get('DOWNLOAD_URL_UBUNTU') % {
        'vernum': vernum,
        'vername': vername
    })
    log = LOG.withField('url', url)

    # Retrieve check sum file
    checksum_url = UBUNTU_URL + '/' + vername + '/current/MD5SUMS'
    resp = requests.get(checksum_url,
                        headers={'User-Agent': util.get_user_agent()})
    if resp.status_code != 200:
        raise exceptions.HTTPError('Failed to fetch %s, status code %d' %
                                   (checksum_url, resp.status_code))

    sum_re = re.compile(r'^([0-9a-f]+) .*' + vername +
                        '-server-cloudimg-amd64.img')
    checksum = None
    for line in resp.text.split('\n'):
        m = sum_re.match(line)
        if m:
            checksum = m.group(1)
            break
    if not checksum_url:
        log.warning('Did not find checksum')

    log.withField('checksum', checksum).info('Checksum check')
    return (url, checksum)
Beispiel #13
0
    def post(self,
             name=None,
             cpus=None,
             memory=None,
             network=None,
             disk=None,
             ssh_key=None,
             user_data=None,
             placed_on=None,
             namespace=None,
             instance_uuid=None):
        global SCHEDULER

        # We need to sanitise the name so its safe for DNS
        name = re.sub(r'([^a-zA-Z0-9_\-])', '', name)

        if not namespace:
            namespace = get_jwt_identity()

        # If accessing a foreign namespace, we need to be an admin
        if get_jwt_identity() not in [namespace, 'system']:
            return error(
                401,
                'only admins can create resources in a different namespace')

        # The instance needs to exist in the DB before network interfaces are created
        if not instance_uuid:
            instance_uuid = str(uuid.uuid4())
            db.add_event('instance', instance_uuid, 'uuid allocated', None,
                         None, None)

        # Create instance object
        instance = virt.from_db(instance_uuid)
        if instance:
            if get_jwt_identity() not in [
                    instance.db_entry['namespace'], 'system'
            ]:
                LOG.info('instance(%s): instance not found, ownership test' %
                         instance_uuid)
                return error(404, 'instance not found')

        if not instance:
            instance = virt.from_definition(uuid=instance_uuid,
                                            name=name,
                                            disks=disk,
                                            memory_mb=memory,
                                            vcpus=cpus,
                                            ssh_key=ssh_key,
                                            user_data=user_data,
                                            owner=namespace)

        if not SCHEDULER:
            SCHEDULER = scheduler.Scheduler()

        # Have we been placed?
        if not placed_on:
            candidates = SCHEDULER.place_instance(instance, network)
            if len(candidates) == 0:
                db.add_event('instance', instance_uuid, 'schedule', 'failed',
                             None, 'insufficient resources')
                db.update_instance_state(instance_uuid, 'error')
                return error(507, 'insufficient capacity')

            placed_on = candidates[0]
            db.place_instance(instance_uuid, placed_on)
            db.add_event('instance', instance_uuid, 'placement', None, None,
                         placed_on)

        else:
            try:
                candidates = SCHEDULER.place_instance(instance,
                                                      network,
                                                      candidates=[placed_on])
                if len(candidates) == 0:
                    db.add_event('instance', instance_uuid, 'schedule',
                                 'failed', None, 'insufficient resources')
                    db.update_instance_state(instance_uuid, 'error')
                    return error(507, 'insufficient capacity')
            except scheduler.CandidateNodeNotFoundException as e:
                return error(404, 'node not found: %s' % e)

        # Have we been placed on a different node?
        if not placed_on == config.parsed.get('NODE_NAME'):
            body = flask_get_post_body()
            body['placed_on'] = placed_on
            body['instance_uuid'] = instance_uuid
            body['namespace'] = namespace

            token = util.get_api_token(
                'http://%s:%d' % (placed_on, config.parsed.get('API_PORT')),
                namespace=namespace)
            r = requests.request('POST',
                                 'http://%s:%d/instances' %
                                 (placed_on, config.parsed.get('API_PORT')),
                                 data=json.dumps(body),
                                 headers={
                                     'Authorization': token,
                                     'User-Agent': util.get_user_agent()
                                 })

            LOG.info('Returning proxied request: %d, %s' %
                     (r.status_code, r.text))
            resp = flask.Response(r.text, mimetype='application/json')
            resp.status_code = r.status_code
            return resp

        # Check we can get the required IPs
        nets = {}
        allocations = {}

        def error_with_cleanup(status_code, message):
            for network_uuid in allocations:
                n = net.from_db(network_uuid)
                for addr, _ in allocations[network_uuid]:
                    with db.get_lock('sf/ipmanager/%s' % n.uuid, ttl=120) as _:
                        ipm = db.get_ipmanager(n.uuid)
                        ipm.release(addr)
                        db.persist_ipmanager(n.uuid, ipm.save())
            return error(status_code, message)

        order = 0
        if network:
            for netdesc in network:
                if 'network_uuid' not in netdesc or not netdesc['network_uuid']:
                    return error_with_cleanup(404, 'network not specified')

                if netdesc['network_uuid'] not in nets:
                    n = net.from_db(netdesc['network_uuid'])
                    if not n:
                        return error_with_cleanup(
                            404,
                            'network %s not found' % netdesc['network_uuid'])
                    nets[netdesc['network_uuid']] = n
                    n.create()

                with db.get_lock('sf/ipmanager/%s' % netdesc['network_uuid'],
                                 ttl=120) as _:
                    db.add_event('network', netdesc['network_uuid'],
                                 'allocate address', None, None, instance_uuid)
                    allocations.setdefault(netdesc['network_uuid'], [])
                    ipm = db.get_ipmanager(netdesc['network_uuid'])
                    if 'address' not in netdesc or not netdesc['address']:
                        netdesc['address'] = ipm.get_random_free_address()
                    else:
                        if not ipm.reserve(netdesc['address']):
                            return error_with_cleanup(
                                409, 'address %s in use' % netdesc['address'])
                    db.persist_ipmanager(netdesc['network_uuid'], ipm.save())
                    allocations[netdesc['network_uuid']].append(
                        (netdesc['address'], order))

                if 'model' not in netdesc or not netdesc['model']:
                    netdesc['model'] = 'virtio'

                db.create_network_interface(str(uuid.uuid4()), netdesc,
                                            instance_uuid, order)

                order += 1

        # Initialise metadata
        db.persist_metadata('instance', instance_uuid, {})

        # Now we can start the instance
        with db.get_lock('sf/instance/%s' % instance.db_entry['uuid'],
                         ttl=900) as lock:
            with util.RecordedOperation('ensure networks exist',
                                        instance) as _:
                for network_uuid in nets:
                    n = nets[network_uuid]
                    n.ensure_mesh()
                    n.update_dhcp()

            with util.RecordedOperation('instance creation', instance) as _:
                instance.create(lock=lock)

            for iface in db.get_instance_interfaces(instance.db_entry['uuid']):
                db.update_network_interface_state(iface['uuid'], 'created')

            return db.get_instance(instance_uuid)