Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key/value backend #624

Closed
wants to merge 50 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
7035abe
Move statefile into state.file module
moretea Mar 13, 2017
6da031f
Introduce different state state schemes.
moretea Mar 13, 2017
8269650
Rename statefile->state in deployment.py
moretea Mar 13, 2017
c73773f
Move file based deployment lock to file state implementation.
moretea Mar 13, 2017
8a4e34a
Fix indenting
moretea Mar 13, 2017
bb056ea
Bite the bullet; remove _db references from business objects + rename…
moretea Mar 13, 2017
6cc171a
move get_resources_for
moretea Mar 13, 2017
813a231
move set_deployment_attrs
moretea Mar 13, 2017
7b58934
move del_deployment_attr
moretea Mar 13, 2017
7ed3fd3
move get_deployment_attr
moretea Mar 13, 2017
0db686d
move create_resource
moretea Mar 13, 2017
97e0714
Move most logic in export() to get_all_deployment_attrs in statefile.
moretea Mar 13, 2017
584d658
Make import use an atomic transaction thingy.
moretea Mar 13, 2017
97bda1a
move clone_deployment
moretea Mar 13, 2017
77e9b93
move part of delete_resource
moretea Mar 13, 2017
4d33f93
missed a few _'s
moretea Mar 13, 2017
5649e39
move part of delete to _delete_deployment
moretea Mar 13, 2017
4075901
use state atomic
moretea Mar 13, 2017
5f39278
Move part of logic of rename to _rename_resource
moretea Mar 13, 2017
6605128
oh yeah, I did rename _db to __db!
moretea Mar 13, 2017
5881c06
Use atomic instead of _db
moretea Mar 13, 2017
de9d700
Use atomic instead of _db
moretea Mar 13, 2017
ff23c1c
move set_resource_attrs
moretea Mar 13, 2017
eb2a24a
move del_resource_attr
moretea Mar 13, 2017
47b3a43
move get_resource_attr
moretea Mar 13, 2017
6835823
move part of export to get_all_resource_attrs
moretea Mar 13, 2017
3739051
Use atomic instead of _db
moretea Mar 13, 2017
66dee1d
oh yeah, I did rename _db to __db!
moretea Mar 13, 2017
6254ccb
simple typo's
moretea Mar 13, 2017
2d1a822
Need to pass in object; too much coupling to refactor right now
moretea Mar 13, 2017
20c5e89
missed assigning to local dict
moretea Mar 13, 2017
6c37bbd
typo
moretea Mar 13, 2017
4baebf9
missing subclass function. Just copied for now
moretea Mar 13, 2017
44652be
typo
moretea Mar 13, 2017
e238874
locally reachable already
moretea Mar 13, 2017
56c39b7
missed the self parameter
moretea Mar 13, 2017
83b5ca1
use _state.atomic instead of _db
moretea Mar 14, 2017
abf3d6e
schema: file -> sqlite3
moretea Mar 14, 2017
0d999cf
Fix _rename_resource, needs resource_id
moretea Mar 14, 2017
a08de62
WIP: initial json backend.
moretea Mar 14, 2017
6baf57f
Include the deployment_uuid in the API for modifying resource attributes
moretea Mar 14, 2017
2dfa148
Small bug fixes
moretea Mar 14, 2017
6733fa7
Utilize deployment_uid
moretea Mar 14, 2017
3827e69
Also recognize json's true as a valid bool value
moretea Mar 14, 2017
056ab60
Undo split between state.atomic and state.db
moretea Mar 29, 2017
38aa181
Typo
moretea Mar 29, 2017
4aaf084
Rename file -> sqlite3_file in tests
moretea Mar 29, 2017
6950f37
Rename file -> sqlite3_file in scripts/nixops
moretea Mar 29, 2017
6576e16
Rename file -> sqlite3_file in scripts/nixops
moretea Mar 29, 2017
9d7cf8c
Merge branch 'master' into kv-state
rbvermaa Jul 24, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions nixops/backends/ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def __init__(self, depl, name, id):

def _reset_state(self):
"""Discard all state pertaining to an instance."""
with self.depl._db:
with self.depl._state.db:
self.state = MachineState.MISSING
self.associate_public_ip_address = None
self.use_private_ip_address = None
Expand Down Expand Up @@ -344,7 +344,7 @@ def _instance_ip_ready(ins):

self.log_end("{0} / {1}".format(instance.ip_address, instance.private_ip_address))

with self.depl._db:
with self.depl._state.db:
self.private_ipv4 = instance.private_ip_address
self.public_ipv4 = instance.ip_address
self.public_dns_name = instance.public_dns_name
Expand Down Expand Up @@ -586,7 +586,7 @@ def _assign_elastic_ip(self, elastic_ipv4, check):

nixops.known_hosts.update(self.public_ipv4, elastic_ipv4, self.public_host_key)

with self.depl._db:
with self.depl._state.db:
self.elastic_ipv4 = elastic_ipv4
self.public_ipv4 = elastic_ipv4
self.ssh_pinged = False
Expand All @@ -599,7 +599,7 @@ def _assign_elastic_ip(self, elastic_ipv4, check):
else:
self.log("address ‘{0}’ was not associated with instance ‘{1}’".format(self.elastic_ipv4, self.vm_id))

with self.depl._db:
with self.depl._state.db:
self.elastic_ipv4 = None
self.public_ipv4 = None
self.ssh_pinged = False
Expand Down Expand Up @@ -663,7 +663,7 @@ def create_instance(self, defn, zone, devmap, user_data, ebs_optimized):
lambda: self._conn.request_spot_instances(price=defn.spot_instance_price/100.0, **common_args)
)[0]

with self.depl._db:
with self.depl._state.db:
self.spot_instance_price = defn.spot_instance_price
self.spot_instance_request_id = request.id

Expand Down Expand Up @@ -694,7 +694,7 @@ def create_instance(self, defn, zone, devmap, user_data, ebs_optimized):
# the instance ID, we'll get the same instance ID on the
# next run.
if not self.client_token:
with self.depl._db:
with self.depl._state.db:
self.client_token = nixops.util.generate_random_string(length=48) # = 64 ASCII chars
self.state = self.STARTING

Expand Down Expand Up @@ -893,7 +893,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
# Generate a public/private host key.
if not self.public_host_key:
(private, public) = nixops.util.create_key_pair(type=defn.host_key_type())
with self.depl._db:
with self.depl._state.db:
self.public_host_key = public
self.private_host_key = private

Expand All @@ -903,7 +903,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):

instance = self.create_instance(defn, zone, devmap, user_data, ebs_optimized)

with self.depl._db:
with self.depl._state.db:
self.vm_id = instance.id
self.ami = defn.ami
self.instance_type = defn.instance_type
Expand Down Expand Up @@ -971,7 +971,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
elastic_ipv4 = res.public_ipv4
self._assign_elastic_ip(elastic_ipv4, check)

with self.depl._db:
with self.depl._state.db:
self.use_private_ip_address = defn.use_private_ip_address
self.associate_public_ip_address = defn.associate_public_ip_address

Expand Down
4 changes: 2 additions & 2 deletions nixops/backends/hetzner.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
self.log_start("creating an exclusive robot admin sub-account "
"for ‘{0}’... ".format(self.name))
server = self._get_server_from_main_robot(self.main_ipv4, defn)
with self.depl._db:
with self.depl._state._db:
(self.robot_admin_user,
self.robot_admin_pass) = server.admin.create()
self.log_end("done. ({0})".format(self.robot_admin_user))
Expand All @@ -609,7 +609,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
)
if robot_user != self.robot_admin_user or \
robot_pass != self.robot_admin_pass:
with self.depl._db:
with self.depl._state._db:
(self.robot_admin_user,
self.robot_admin_pass) = (robot_user, robot_pass)

Expand Down
4 changes: 2 additions & 2 deletions nixops/backends/virtualbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
# Generate a public/private host key.
if not self.public_host_key:
(private, public) = nixops.util.create_key_pair()
with self.depl._db:
with self.depl._state.db:
self.public_host_key = public
self.private_host_key = private

Expand All @@ -203,7 +203,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):

# Backwards compatibility.
if self.disk:
with self.depl._db:
with self.depl._state.db:
self._update_disk("disk1", {"created": True, "path": self.disk,
"attached": self.disk_attached,
"port": 0})
Expand Down
121 changes: 24 additions & 97 deletions nixops/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import errno
from collections import defaultdict
from xml.etree import ElementTree
import nixops.statefile
import nixops.backends
import nixops.logger
import nixops.parallel
Expand All @@ -22,7 +21,6 @@
import getpass
import traceback
import glob
import fcntl
import itertools
import platform
from nixops.util import ansi_success
Expand Down Expand Up @@ -50,9 +48,8 @@ class Deployment(object):
configs_path = nixops.util.attr_property("configsPath", None)
rollback_enabled = nixops.util.attr_property("rollbackEnabled", False)

def __init__(self, statefile, uuid, log_file=sys.stderr):
self._statefile = statefile
self._db = statefile._db
def __init__(self, state, uuid, log_file=sys.stderr):
self._state = state
self.uuid = uuid

self._last_log_prefix = None
Expand All @@ -72,15 +69,8 @@ def __init__(self, statefile, uuid, log_file=sys.stderr):
if not os.path.exists(self.expr_path):
self.expr_path = os.path.dirname(__file__) + "/../nix"

self.resources = {}
with self._db:
c = self._db.cursor()
c.execute("select id, name, type from Resources where deployment = ?", (self.uuid,))
for (id, name, type) in c.fetchall():
r = _create_state(self, type, name, id)
self.resources[name] = r
self.resources = self._state.get_resources_for(self)
self.logger.update_log_prefixes()

self.definitions = None


Expand Down Expand Up @@ -120,65 +110,41 @@ def get_machine(self, name):
raise Exception("resource ‘{0}’ is not a machine".format(name))
return res


def _set_attrs(self, attrs):
"""Update deployment attributes in the state file."""
with self._db:
c = self._db.cursor()
for n, v in attrs.iteritems():
if v == None:
c.execute("delete from DeploymentAttrs where deployment = ? and name = ?", (self.uuid, n))
else:
c.execute("insert or replace into DeploymentAttrs(deployment, name, value) values (?, ?, ?)",
(self.uuid, n, v))
"""Update deployment attributes in the state."""
self._state.set_deployment_attrs(self.uuid, attrs)


def _set_attr(self, name, value):
"""Update one deployment attribute in the state file."""
"""Update one deployment attribute in the state."""
self._set_attrs({name: value})


def _del_attr(self, name):
"""Delete a deployment attribute from the state file."""
with self._db:
self._db.execute("delete from DeploymentAttrs where deployment = ? and name = ?", (self.uuid, name))
"""Delete a deployment attribute from the state."""
self._state.del_deployment_attr(self.uuid, name)


#TODO(moretea): The default param does not appear to be used at all?
# Removed it when moving the body to nixops/state/file.py.
def _get_attr(self, name, default=nixops.util.undefined):
"""Get a deployment attribute from the state file."""
with self._db:
c = self._db.cursor()
c.execute("select value from DeploymentAttrs where deployment = ? and name = ?", (self.uuid, name))
row = c.fetchone()
if row != None: return row[0]
return nixops.util.undefined

"""Get a deployment attribute from the state."""
return self._state.get_deployment_attr(self.uuid, name)

def _create_resource(self, name, type):
c = self._db.cursor()
c.execute("select 1 from Resources where deployment = ? and name = ?", (self.uuid, name))
if len(c.fetchall()) != 0:
raise Exception("resource already exists in database!")
c.execute("insert into Resources(deployment, name, type) values (?, ?, ?)",
(self.uuid, name, type))
id = c.lastrowid
r = _create_state(self, type, name, id)
r = self._state.create_resource(self, name, type)
self.resources[name] = r
return r


def export(self):
with self._db:
c = self._db.cursor()
c.execute("select name, value from DeploymentAttrs where deployment = ?", (self.uuid,))
rows = c.fetchall()
res = {row[0]: row[1] for row in rows}
res['resources'] = {r.name: r.export() for r in self.resources.itervalues()}
return res
res = self._state.get_all_deployment_attrs(self.uuid)
res['resources'] = {r.name: r.export() for r in self.resources.itervalues()}
return res


def import_(self, attrs):
with self._db:
with self._state.db:
for k, v in attrs.iteritems():
if k == 'resources': continue
self._set_attr(k, v)
Expand All @@ -189,49 +155,21 @@ def import_(self, attrs):


def clone(self):
with self._db:
new = self._statefile.create_deployment()
self._db.execute("insert into DeploymentAttrs (deployment, name, value) " +
"select ?, name, value from DeploymentAttrs where deployment = ?",
(new.uuid, self.uuid))
new.configs_path = None
return new
return self._state.clone_deployment(self.uuid)


def _get_deployment_lock(self):
if self._lock_file_path is None:
lock_dir = os.environ.get("HOME", "") + "/.nixops/locks"
if not os.path.exists(lock_dir): os.makedirs(lock_dir, 0700)
self._lock_file_path = lock_dir + "/" + self.uuid
class DeploymentLock(object):
def __init__(self, depl):
self._lock_file_path = depl._lock_file_path
self._logger = depl.logger
self._lock_file = None
def __enter__(self):
self._lock_file = open(self._lock_file_path, "w")
fcntl.fcntl(self._lock_file, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
try:
fcntl.flock(self._lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
self._logger.log(
"waiting for exclusive deployment lock..."
)
fcntl.flock(self._lock_file, fcntl.LOCK_EX)
def __exit__(self, exception_type, exception_value, exception_traceback):
self._lock_file.close()
return DeploymentLock(self)
return self._state.get_deployment_lock(self)


def delete_resource(self, m):
del self.resources[m.name]
with self._db:
self._db.execute("delete from Resources where deployment = ? and id = ?", (self.uuid, m.id))
self._state.delete_resource(self.uuid, m.id)


def delete(self, force=False):
"""Delete this deployment from the state file."""
with self._db:
with self._state.db:
if not force and len(self.resources) > 0:
raise Exception("cannot delete this deployment because it still has resources")

Expand All @@ -242,7 +180,7 @@ def delete(self, force=False):
if os.path.islink(p): os.remove(p)

# Delete the deployment from the database.
self._db.execute("delete from Deployments where uuid = ?", (self.uuid,))
self._state._delete_deployment(self.uuid)


def _nix_path_flags(self):
Expand Down Expand Up @@ -836,7 +774,7 @@ def evaluate_active(self, include=[], exclude=[], kill_obsolete=False):
self.evaluate()

# Create state objects for all defined resources.
with self._db:
with self._state.db:
for m in self.definitions.itervalues():
if m.name not in self.resources:
self._create_resource(m.name, m.get_type())
Expand Down Expand Up @@ -1141,9 +1079,7 @@ def rename(self, name, new_name):

m = self.resources.pop(name)
self.resources[new_name] = m

with self._db:
self._db.execute("update Resources set name = ? where deployment = ? and id = ?", (new_name, self.uuid, m.id))
self._state._rename_resource(self.uuid, m.id, new_name)


def send_keys(self, include=[], exclude=[]):
Expand Down Expand Up @@ -1188,15 +1124,6 @@ def _create_definition(xml, config, type_name):

raise nixops.deployment.UnknownBackend("unknown resource type ‘{0}’".format(type_name))

def _create_state(depl, type, name, id):
"""Create a resource state object of the desired type."""

for cls in _subclasses(nixops.resources.ResourceState):
if type == cls.get_type():
return cls(depl, name, id)

raise nixops.deployment.UnknownBackend("unknown resource type ‘{0}’".format(type))


# Automatically load all resource types.
def _load_modules_from(dir):
Expand Down
34 changes: 10 additions & 24 deletions nixops/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,46 +64,32 @@ def __init__(self, depl, name, id):

def _set_attrs(self, attrs):
"""Update machine attributes in the state file."""
with self.depl._db:
c = self.depl._db.cursor()
for n, v in attrs.iteritems():
if v == None:
c.execute("delete from ResourceAttrs where machine = ? and name = ?", (self.id, n))
else:
c.execute("insert or replace into ResourceAttrs(machine, name, value) values (?, ?, ?)",
(self.id, n, v))
self.depl._state.set_resource_attrs(self.depl.uuid, self.id, attrs)


def _set_attr(self, name, value):
"""Update one machine attribute in the state file."""
self._set_attrs({name: value})

def _del_attr(self, name):
"""Delete a machine attribute from the state file."""
with self.depl._db:
self.depl._db.execute("delete from ResourceAttrs where machine = ? and name = ?", (self.id, name))
self.depl._state.del_resource_attr(self.depl.uuid, self.id, name)

#TODO(moretea): again, the default option appears to be defunct.
# Have removed it in state/file.py.
def _get_attr(self, name, default=nixops.util.undefined):
"""Get a machine attribute from the state file."""
with self.depl._db:
c = self.depl._db.cursor()
c.execute("select value from ResourceAttrs where machine = ? and name = ?", (self.id, name))
row = c.fetchone()
if row != None: return row[0]
return nixops.util.undefined
return self.depl._state.get_resource_attr(self.depl.uuid, self.id, name)

def export(self):
"""Export the resource to move between databases"""
with self.depl._db:
c = self.depl._db.cursor()
c.execute("select name, value from ResourceAttrs where machine = ?", (self.id,))
rows = c.fetchall()
res = {row[0]: row[1] for row in rows}
res['type'] = self.get_type()
return res
res = self.depl._state.get_all_resource_attrs(self.depl.uuid, self.id)
res['type'] = self.get_type()
return res

def import_(self, attrs):
"""Import the resource from another database"""
with self.depl._db:
with self.depl._state.db:
for k, v in attrs.iteritems():
if k == 'type': continue
self._set_attr(k, v)
Expand Down
Loading