blob: f5632fb9f0da6bfa7f01e20bb8f53d2b1c4f9cc2 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
import time
import kopf, logging
import openserverless.kube as kube
import openserverless.kustomize as kus
import openserverless.config as cfg
import openserverless.util as util
import openserverless.operator_util as operator_util
import openserverless.minio_util as mutil
import openserverless.openwhisk as openwhisk
from openserverless.milvus_admin_client import MilvusAdminClient
from openserverless.user_config import UserConfig
from openserverless.user_metadata import UserMetadata
from openserverless.seaweedfs_util import SeaweedfsClient
def patchEntries(data: dict):
src_folder = "milvus"
if data['slim']:
src_folder += "-slim"
tplp = ["milvus-cfg-slim-base.yaml"]
else:
tplp = ["milvus-cfg-base.yaml"]
tplp.append("milvus.yaml")
if (data['affinity'] or data['tolerations']):
tplp.append("affinity-tolerance-dep-core-attach.yaml")
kust = kus.patchTemplates(src_folder, tplp, data)
kust += kus.patchGenericEntry("Secret", "openserverless-milvus-etcd-secret", "/data/username",
util.b64_encode(data['milvus_etcd_username']))
kust += kus.patchGenericEntry("Secret", "openserverless-milvus-etcd-secret", "/data/password",
util.b64_encode(data['milvus_etcd_password']))
kust += kus.patchGenericEntry("Secret", "openserverless-milvus-s3-secret", "/stringData/accesskey",
data['milvus_s3_username'])
kust += kus.patchGenericEntry("Secret", "openserverless-milvus-s3-secret", "/stringData/secretkey",
data['milvus_s3_password'])
kust += kus.patchGenericEntry("PersistentVolumeClaim", "openserverless-milvus", "/spec/storageClassName",
data['storageClass'])
kust += kus.patchGenericEntry("PersistentVolumeClaim", "openserverless-milvus", "/spec/resources/requests/storage",
f"{data['size']}Gi")
if not data["slim"]:
kust += kus.patchGenericEntry("PersistentVolumeClaim", "openserverless-milvus-zookeeper", "/spec/storageClassName",
data['storageClass'])
kust += kus.patchGenericEntry("PersistentVolumeClaim", "openserverless-milvus-zookeeper",
"/spec/resources/requests/storage", f"{data['zookeeper_size']}Gi")
kust += kus.patchGenericEntry("PersistentVolumeClaim", "openserverless-milvus-bookie-journal", "/spec/storageClassName",
data['storageClass'])
kust += kus.patchGenericEntry("PersistentVolumeClaim", "openserverless-milvus-bookie-journal",
"/spec/resources/requests/storage", f"{data['bookie_journal_size']}Gi")
kust += kus.patchGenericEntry("PersistentVolumeClaim", "openserverless-milvus-bookie-ledgers", "/spec/storageClassName",
data['storageClass'])
kust += kus.patchGenericEntry("PersistentVolumeClaim", "openserverless-milvus-bookie-ledgers",
"/spec/resources/requests/storage", f"{data['bookie_ledgers_size']}Gi")
return kust
def create(owner=None):
"""
Deploys the milvus vector db in standalone mode.
"""
data = util.get_milvus_config_data()
res = create_milvus_accounts(data)
dir = "milvus"
if res:
if data['slim']:
dir += "-slim"
logging.info(f"*** creating a {dir} standalone instance")
kust = patchEntries(data)
mspec = kus.kustom_list(dir, kust, templates=[], data=data)
if owner:
kopf.append_owner_reference(mspec['items'], owner)
else:
cfg.put("state.milvus.spec", mspec)
kube.apply(mspec)
util.wait_for_pod_ready(
r"{.items[?(@.metadata.labels.app\.kubernetes\.io\/instance == 'openserverless-milvus')].metadata.name}")
milvus_api_host = cfg.get("milvus.host", "MILVUS_API_HOST", "openserverless-milvus")
milvus_api_port = cfg.get("milvus.port", "MILVUS_API_PORT", "19530")
logging.info("*** waiting for milvus api to be available")
util.wait_for_http(f"http://{milvus_api_host}:{milvus_api_port}", up_statuses=[200,401], timeout=30)
res = create_default_milvus_database(data)
logging.info("*** created a milvus standalone instance")
return res
def create_minio_milvus_account(data: dict):
""""
Creates technical accounts for MINIO
"""
try:
minioClient = mutil.MinioClient()
bucket_policy_names = []
bucket_policy_names.append(f"{data['milvus_bucket_name']}/*")
res = util.check(minioClient.add_user(data["milvus_s3_username"], data["milvus_s3_password"]),
"create_milvus_s3_user", True)
res = util.check(minioClient.make_bucket(data["milvus_bucket_name"]), "create_milvus_s3_bucket", res)
return util.check(minioClient.assign_rw_bucket_policy_to_user(data["milvus_s3_username"], bucket_policy_names),
"assign_milvus_s3_bucket_policy", res)
except Exception as ex:
logging.error("Could not create milvus MINIO accounts", ex)
return False
def create_seaweedfs_milvus_account(data: dict):
""""
Creates technical accounts for SEAWEEDFS
"""
try:
seaweedfsClient = SeaweedfsClient()
res = util.check(seaweedfsClient.make_bucket(data["milvus_bucket_name"],data["milvus_bucket_quota"]),"make_milvus_bucket",True)
return util.check(seaweedfsClient.add_user(data["milvus_s3_username"],data["milvus_s3_username"],data["milvus_s3_password"],data["milvus_bucket_name"]),"add_milvus_user",res)
except Exception as ex:
logging.error("Could not create milvus SEAWEEDFS accounts", ex)
return False
def create_milvus_accounts(data: dict):
""""
Creates technical accounts for ETCD and MINIO
"""
# currently we use the ETCD root password, so we skip the ETCD user creation.
# res = util.check(etcd.create_etcd_user(data['milvus_etcd_username'],data['milvus_etcd_password'],data['milvus_etcd_prefix']),"create_etcd_milvus_user",True)
if cfg.get('components.minio'):
return create_minio_milvus_account(data)
if cfg.get('components.seaweedfs'):
return create_seaweedfs_milvus_account(data)
def delete_minio_milvus_account(data: dict):
"""
Deletes technical accounts for MINIO
"""
try:
logging.info("removing milvus minio technical accounts.")
minioClient = mutil.MinioClient()
res = util.check(minioClient.remove_user(data["milvus_s3_username"]), "remove_user", True)
return util.check(minioClient.force_bucket_remove(data["milvus_bucket_name"]), "force_bucket_remove", res)
except Exception as ex:
logging.error("Could not delete milvus MINIO accounts", ex)
return False
def delete_seaweedfs_milvus_account(data: dict):
"""
Delete technical accounts for SEAWEEDFS
"""
try:
logging.info("removing milvus seaweedfs technical accounts.")
seaweedfsClient = SeaweedfsClient()
res = util.check(seaweedfsClient.delete_user(data["milvus_s3_username"]), "delete_user", True)
return util.check(seaweedfsClient.force_bucket_remove(data["milvus_bucket_name"]), "force_bucket_remove", res)
except Exception as ex:
logging.error("Could not delete milvus SEAWEEDFS accounts", ex)
return False
def delete_milvus_accounts(data: dict):
""""
Deletes technical accounts for ETCD and MINIO
"""
# currently we use the ETCD root password, so we skip the ETCD user deletion.
logging.info("removing milvus technical accounts.")
if cfg.get('components.minio'):
return delete_minio_milvus_account(data)
if cfg.get('components.seaweedfs'):
return delete_seaweedfs_milvus_account(data)
def create_default_milvus_database(data):
"""
Creates openserverless MILVUS custom resources
"""
logging.info("*** configuring MILVUS database for openserverless")
adminClient = MilvusAdminClient()
res = adminClient.setup_user("openserverless", data["openserverless_password"], "openserverless")
if (res):
_annotate_nuv_milvus_metadata(data)
logging.info("*** configured MILVUS database for openserverless")
return True
return False
def _annotate_nuv_milvus_metadata(data):
"""
annotate openserverless configmap with entries for MILVUS connectivity MILVUS_HOST, MILVUS_PORT, MILVUS_TOKEN, MILVUS_DB_NAME
this is becasue MINIO
"""
try:
milvus_service = util.get_service(
r"{.items[?(@.metadata.labels.app\.kubernetes\.io\/instance == 'openserverless-milvus')]}")
if (milvus_service):
milvus_host = f"{milvus_service['metadata']['name']}.{milvus_service['metadata']['namespace']}.svc.cluster.local"
password = data["openserverless_password"]
openwhisk.annotate(f"milvus_host={milvus_host}")
openwhisk.annotate(f"milvus_token=openserverless:{password}")
openwhisk.annotate("milvus_db_name=openserverless")
ports = list(milvus_service['spec']['ports'])
for port in ports:
if (port['name'] == 'milvus'):
openwhisk.annotate(f"milvus_port={port['port']}")
return None
except Exception as e:
logging.error(f"failed to annotate MILVUS for openserverless: {e}")
return None
def _add_milvus_user_metadata(ucfg: UserConfig, user_metadata: UserMetadata):
"""
adds entries for MILVUS connectivity MILVUS_HOST, MILVUS_PORT, MILVUS_TOKEN, MILVUS_DB_NAME
"""
try:
milvus_service = util.get_service(
r"{.items[?(@.metadata.labels.app\.kubernetes\.io\/instance == 'openserverless-milvus')]}")
if (milvus_service):
milvus_host = f"{milvus_service['metadata']['name']}.{milvus_service['metadata']['namespace']}.svc.cluster.local"
milvus_token = f"{ucfg.get('namespace')}:{ucfg.get('milvus.password')}"
user_metadata.add_metadata("MILVUS_HOST", milvus_host)
user_metadata.add_metadata("MILVUS_TOKEN", milvus_token)
user_metadata.add_metadata("MILVUS_DB_NAME", ucfg.get('milvus.database'))
ports = list(milvus_service['spec']['ports'])
for port in ports:
if (port['name'] == 'milvus'):
user_metadata.add_metadata("MILVUS_PORT", port['port'])
return None
except Exception as e:
logging.error(f"failed to build MILVUS metadata for {ucfg.get('namespace')}: {e}")
return None
def create_ow_milvus(ucfg: UserConfig, user_metadata: UserMetadata, owner=None):
logging.info(f"*** configuring MILVUS database for {ucfg.get('namespace')}")
adminClient = MilvusAdminClient()
username = ucfg.get("namespace")
password = ucfg.get("milvus.password")
database = ucfg.get("milvus.database")
res = adminClient.setup_user(username, password, database)
if (res):
_add_milvus_user_metadata(ucfg, user_metadata)
logging.info(f"*** configured MILVUS database linked to namespace {ucfg.get('namespace')}")
return res
def delete_ow_milvus(ucfg):
logging.info(f"removing MILVUS database {ucfg.get('namespace')}")
adminClient = MilvusAdminClient()
res = adminClient.remove_user(ucfg.get('namespace'), ucfg.get('milvus.database'))
if res:
logging.info(f"removed MILVUS database linked to namespace {ucfg.get('namespace')}")
return res
def delete_by_owner(data):
dir = "milvus"
if data['slim']:
dir += "-slim"
spec = kus.build(dir)
res = kube.delete(spec)
logging.info(f"delete milvus: {res}")
return res
def delete_by_spec():
spec = cfg.get("state.milvus.spec")
if spec:
res = kube.delete(spec)
logging.info(f"delete milvus: {res}")
return res
def delete(owner=None):
data = util.get_milvus_config_data()
if owner:
res = delete_by_owner(data)
else:
res = delete_by_spec()
return delete_milvus_accounts(data)
def patch(status, action, owner=None):
"""
Called by the operator patcher to create/delete milvus component
"""
try:
logging.info(f"*** handling request to {action} milvus")
if action == 'create':
msg = create(owner)
operator_util.patch_operator_status(status, 'milvus', 'on')
else:
msg = delete(owner)
operator_util.patch_operator_status(status, 'milvus', 'off')
logging.info(msg)
logging.info(f"*** handled request to {action} milvus")
except Exception as e:
logging.error('*** failed to update milvus: %s' % e)
operator_util.patch_operator_status(status, 'milvus', 'error')